1use cli_core::eval::escape_str;
11use cli_core::reports::coerce_date_arg;
12use cli_core::{CommandError, CommandNode};
13use sqlx::types::Uuid;
14use std::str::FromStr;
15
16use crate::tabs::reports::ReportKind;
17
18pub struct ReportRequest {
20 pub kind: ReportKind,
21 pub from: String,
23 pub to: String,
25 pub chart: String,
26}
27
28pub fn build_report_request(
38 path: &[String],
39 args: &[(String, String)],
40) -> Result<Option<ReportRequest>, CommandError> {
41 let kind = match path_key(path).as_str() {
42 "reports balance" => ReportKind::Balance,
43 "reports activity" => ReportKind::Activity,
44 "reports breakdown" => ReportKind::Breakdown,
45 _ => return Ok(None),
46 };
47 let chart = opt_arg(args, "chart").unwrap_or_else(|| "bar".to_string());
48 if kind == ReportKind::Balance {
49 return Ok(Some(ReportRequest {
50 kind,
51 from: String::new(),
52 to: String::new(),
53 chart,
54 }));
55 }
56 let from_raw = opt_arg(args, "from");
57 let to_raw = opt_arg(args, "to");
58 match (from_raw, to_raw) {
59 (Some(f), Some(t)) => {
60 let from = coerce_date_arg(&f, false)?;
61 let to = coerce_date_arg(&t, true)?;
62 Ok(Some(ReportRequest {
63 kind,
64 from,
65 to,
66 chart,
67 }))
68 }
69 _ => Ok(None),
70 }
71}
72
73pub fn build_form(
82 _node: &CommandNode,
83 path: &[String],
84 args: &[(String, String)],
85) -> Result<Option<String>, CommandError> {
86 match path_key(path).as_str() {
88 "version" => Ok(Some("(get-version)".to_string())),
89
90 "account list" => Ok(Some("(list-accounts)".to_string())),
91 "account balance" => {
92 let account = require_uuid(args, "account")?;
93 Ok(Some(format!(
94 "(get-balances {})",
95 escape_str(&account.to_string())
96 )))
97 }
98 "account create" | "account tag" => Ok(None),
100
101 "transaction list" => {
102 let account = opt_uuid(args, "account")?;
103 Ok(Some(format!(
104 "(list-transactions {})",
105 escape_str(&account)
106 )))
107 }
108 "transaction create" | "transaction tag" => Ok(None),
110
111 "commodity list" => Ok(Some("(list-commodities)".to_string())),
112 "commodity create" | "commodity convert" => Ok(None),
114
115 "config get" => {
116 let name = require_str(args, "name")?;
117 Ok(Some(format!("(get-config {})", escape_str(&name))))
118 }
119 "config set" => Ok(None),
121
122 _ => Ok(None),
124 }
125}
126
127fn path_key(path: &[String]) -> String {
129 path.join(" ")
130}
131
132fn opt_arg(args: &[(String, String)], key: &str) -> Option<String> {
135 args.iter().find(|(k, _)| k == key).map(|(_, v)| v.clone())
136}
137
138fn require_str(args: &[(String, String)], key: &str) -> Result<String, CommandError> {
139 opt_arg(args, key)
140 .filter(|v| !v.is_empty())
141 .ok_or_else(|| CommandError::Argument(format!("missing required arg: {key}")))
142}
143
144fn require_uuid(args: &[(String, String)], key: &str) -> Result<Uuid, CommandError> {
145 let s = require_str(args, key)?;
146 Uuid::from_str(&s).map_err(|_| CommandError::Argument(format!("invalid UUID for {key}: {s}")))
147}
148
149fn opt_uuid(args: &[(String, String)], key: &str) -> Result<String, CommandError> {
153 match opt_arg(args, key).filter(|v| !v.is_empty()) {
154 None => Ok(String::new()),
155 Some(s) => Uuid::from_str(&s)
156 .map(|u| u.to_string())
157 .map_err(|_| CommandError::Argument(format!("invalid UUID for {key}: {s}"))),
158 }
159}
160
161#[cfg(test)]
162mod tests;