envelope_cli/cli/
export.rs

1//! CLI commands for data export
2//!
3//! Provides commands for exporting data in various formats.
4
5use crate::error::EnvelopeResult;
6use crate::export::{csv, json, yaml};
7use crate::storage::Storage;
8use clap::{Subcommand, ValueEnum};
9use std::fs::File;
10use std::io::BufWriter;
11use std::path::PathBuf;
12
13/// Export format options
14#[derive(Debug, Clone, Copy, ValueEnum)]
15pub enum ExportFormat {
16    /// CSV format (transactions only)
17    Csv,
18    /// JSON format (full database)
19    Json,
20    /// YAML format (full database, human-readable)
21    Yaml,
22}
23
24/// Export subcommands
25#[derive(Subcommand, Debug)]
26pub enum ExportCommands {
27    /// Export all data to a file
28    All {
29        /// Output file path
30        output: PathBuf,
31
32        /// Export format
33        #[arg(short, long, value_enum, default_value = "json")]
34        format: ExportFormat,
35
36        /// Pretty-print JSON output
37        #[arg(long)]
38        pretty: bool,
39    },
40
41    /// Export transactions to CSV
42    Transactions {
43        /// Output file path
44        output: PathBuf,
45    },
46
47    /// Export budget allocations to CSV
48    Allocations {
49        /// Output file path
50        output: PathBuf,
51
52        /// Number of months to export (default: 12)
53        #[arg(short, long, default_value = "12")]
54        months: usize,
55    },
56
57    /// Export accounts to CSV
58    Accounts {
59        /// Output file path
60        output: PathBuf,
61    },
62
63    /// Show export information without writing files
64    Info,
65}
66
67/// Handle export commands
68pub fn handle_export_command(storage: &Storage, cmd: ExportCommands) -> EnvelopeResult<()> {
69    match cmd {
70        ExportCommands::All {
71            output,
72            format,
73            pretty,
74        } => handle_export_all(storage, output, format, pretty),
75        ExportCommands::Transactions { output } => handle_export_transactions(storage, output),
76        ExportCommands::Allocations { output, months } => {
77            handle_export_allocations(storage, output, months)
78        }
79        ExportCommands::Accounts { output } => handle_export_accounts(storage, output),
80        ExportCommands::Info => handle_export_info(storage),
81    }
82}
83
84/// Handle full export
85fn handle_export_all(
86    storage: &Storage,
87    output: PathBuf,
88    format: ExportFormat,
89    pretty: bool,
90) -> EnvelopeResult<()> {
91    let file = File::create(&output).map_err(|e| {
92        crate::error::EnvelopeError::Export(format!(
93            "Failed to create file {}: {}",
94            output.display(),
95            e
96        ))
97    })?;
98    let mut writer = BufWriter::new(file);
99
100    match format {
101        ExportFormat::Csv => {
102            // For CSV, export transactions as the primary data
103            csv::export_transactions_csv(storage, &mut writer)?;
104            println!("Transactions exported to: {}", output.display());
105            println!("Note: CSV format exports transactions only. Use JSON or YAML for full database export.");
106        }
107        ExportFormat::Json => {
108            json::export_full_json(storage, &mut writer, pretty)?;
109            println!("Full database exported to: {}", output.display());
110        }
111        ExportFormat::Yaml => {
112            yaml::export_full_yaml(storage, &mut writer)?;
113            println!("Full database exported to: {}", output.display());
114        }
115    }
116
117    Ok(())
118}
119
120/// Handle transactions export
121fn handle_export_transactions(storage: &Storage, output: PathBuf) -> EnvelopeResult<()> {
122    let file = File::create(&output).map_err(|e| {
123        crate::error::EnvelopeError::Export(format!(
124            "Failed to create file {}: {}",
125            output.display(),
126            e
127        ))
128    })?;
129    let mut writer = BufWriter::new(file);
130
131    csv::export_transactions_csv(storage, &mut writer)?;
132
133    let count = storage.transactions.get_all()?.len();
134    println!("Exported {} transactions to: {}", count, output.display());
135
136    Ok(())
137}
138
139/// Handle allocations export
140fn handle_export_allocations(
141    storage: &Storage,
142    output: PathBuf,
143    months: usize,
144) -> EnvelopeResult<()> {
145    use crate::models::BudgetPeriod;
146
147    let file = File::create(&output).map_err(|e| {
148        crate::error::EnvelopeError::Export(format!(
149            "Failed to create file {}: {}",
150            output.display(),
151            e
152        ))
153    })?;
154    let mut writer = BufWriter::new(file);
155
156    // Generate periods
157    let current = BudgetPeriod::current_month();
158    let periods: Vec<_> = (0..months)
159        .map(|i| {
160            let mut p = current.clone();
161            for _ in 0..i {
162                p = p.prev();
163            }
164            p
165        })
166        .collect();
167
168    csv::export_allocations_csv(storage, &mut writer, Some(periods))?;
169
170    println!(
171        "Exported {} months of budget allocations to: {}",
172        months,
173        output.display()
174    );
175
176    Ok(())
177}
178
179/// Handle accounts export
180fn handle_export_accounts(storage: &Storage, output: PathBuf) -> EnvelopeResult<()> {
181    let file = File::create(&output).map_err(|e| {
182        crate::error::EnvelopeError::Export(format!(
183            "Failed to create file {}: {}",
184            output.display(),
185            e
186        ))
187    })?;
188    let mut writer = BufWriter::new(file);
189
190    csv::export_accounts_csv(storage, &mut writer)?;
191
192    let count = storage.accounts.get_all()?.len();
193    println!("Exported {} accounts to: {}", count, output.display());
194
195    Ok(())
196}
197
198/// Show export information
199fn handle_export_info(storage: &Storage) -> EnvelopeResult<()> {
200    let export = json::FullExport::from_storage(storage)?;
201
202    println!("Export Information");
203    println!("==================\n");
204
205    println!("Schema Version: {}", export.schema_version);
206    println!("App Version:    {}", export.app_version);
207    println!();
208
209    println!("Data Summary:");
210    println!("  Accounts:      {}", export.metadata.account_count);
211    println!("  Transactions:  {}", export.metadata.transaction_count);
212    println!("  Categories:    {}", export.metadata.category_count);
213    println!("  Allocations:   {}", export.metadata.allocation_count);
214    println!("  Payees:        {}", export.metadata.payee_count);
215    println!();
216
217    if let Some(earliest) = &export.metadata.earliest_transaction {
218        println!("Transaction Date Range:");
219        println!("  Earliest: {}", earliest);
220    }
221    if let Some(latest) = &export.metadata.latest_transaction {
222        println!("  Latest:   {}", latest);
223    }
224
225    println!("\nAvailable Export Formats:");
226    println!("  csv  - CSV format (transactions, allocations, or accounts)");
227    println!("  json - JSON format (full database, machine-readable)");
228    println!("  yaml - YAML format (full database, human-readable)");
229
230    println!("\nExamples:");
231    println!("  envelope export all backup.json --format json --pretty");
232    println!("  envelope export transactions txns.csv");
233    println!("  envelope export accounts accounts.csv");
234
235    Ok(())
236}