Skip to main content

tiller_sync/commands/
mod.rs

1//! Command handlers for the tiller CLI.
2//!
3//! This module contains implementations for all CLI subcommands.
4
5mod auth;
6mod delete;
7mod init;
8mod insert;
9mod mcp;
10pub mod query;
11mod sync;
12mod update;
13
14use serde::Serialize;
15use std::fmt::Debug;
16use tracing::{debug, info};
17
18pub use auth::{auth, auth_verify};
19pub use delete::{delete_autocats, delete_categories, delete_transactions};
20pub use init::init;
21pub use insert::{insert_autocat, insert_category, insert_transaction};
22pub use mcp::mcp;
23pub use query::{query, schema, ColumnInfo, ForeignKeyInfo, IndexInfo, Rows, Schema, TableInfo};
24pub use sync::{sync_down, sync_up};
25pub use update::{update_autocats, update_categories, update_transactions};
26
27/// The output type for a command. This allows the command to return a consistent message and,
28/// optionally, structured data to both the command line and MCP server interfaces.
29#[derive(Debug, Clone, Serialize)]
30pub struct Out<T>
31where
32    T: Serialize + Clone + Debug,
33{
34    /// A message that can be printed to the user regarding the outcome of the command execution.
35    message: String,
36
37    /// Any structured data that needs to be output from the call.
38    structure: Option<T>,
39}
40
41impl<T, S> From<S> for Out<T>
42where
43    T: Debug + Clone + Serialize,
44    S: Into<String>,
45{
46    fn from(value: S) -> Self {
47        Out::new_message(value)
48    }
49}
50
51/// Controls how formulas are handled during `sync up`.
52#[derive(
53    Debug,
54    Clone,
55    Copy,
56    PartialEq,
57    Eq,
58    Default,
59    serde::Serialize,
60    serde::Deserialize,
61    schemars::JsonSchema,
62    clap::ValueEnum,
63)]
64#[serde(rename_all = "lowercase")]
65pub enum FormulasMode {
66    /// Default: error if formulas exist, prompting user to choose preserve or ignore.
67    #[default]
68    Unknown,
69    /// Preserve formulas by writing them back to their original cell positions.
70    Preserve,
71    /// Ignore all formulas; only write values.
72    Ignore,
73}
74
75serde_plain::derive_display_from_serialize!(FormulasMode);
76serde_plain::derive_fromstr_from_deserialize!(FormulasMode);
77
78impl<T> Out<T>
79where
80    T: Serialize + Clone + Debug,
81{
82    /// Create a new `Out` object that has `Some(structure)`.
83    pub fn new<S>(message: S, structure: T) -> Self
84    where
85        S: Into<String>,
86    {
87        Self {
88            message: message.into(),
89            structure: Some(structure),
90        }
91    }
92
93    /// Create a new `Out` object that has `None` for `structure`.
94    pub fn new_message<S>(message: S) -> Self
95    where
96        S: Into<String>,
97    {
98        Self {
99            message: message.into(),
100            structure: None,
101        }
102    }
103
104    /// Get the `message`.
105    pub fn message(&self) -> &str {
106        &self.message
107    }
108
109    /// Get the structured data stored in `structure`.
110    pub fn structure(&self) -> Option<&T> {
111        self.structure.as_ref()
112    }
113
114    /// Print the message to `info!` and the structured data (if it exists) as JSON to `debug!`.
115    pub fn print(&self) {
116        info!("{}", self.message);
117        if let Some(structure) = self.structure() {
118            if let Ok(json) = serde_json::to_string_pretty(structure) {
119                debug!("Command output:\n\n{json}\n\n");
120            }
121        }
122    }
123}