summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgarhve <git@garhve.com>2024-08-01 02:02:43 +0800
committergarhve <git@garhve.com>2024-08-01 02:02:43 +0800
commitc926cd6eceaa2522b557c3dc1f9d7ebe8002702d (patch)
tree7f4adab86870dcb2fecf797d0b10bf0aa3a01759
add function to print project summary
-rw-r--r--Cargo.lock308
-rw-r--r--Cargo.toml10
-rw-r--r--src/.drawing.rs.swpbin0 -> 12288 bytes
-rw-r--r--src/.lib.rs.swpbin0 -> 20480 bytes
-rw-r--r--src/drawing.rs77
-rw-r--r--src/lib.rs144
-rw-r--r--src/main.rs10
-rw-r--r--src/record.rs72
8 files changed, 621 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1e2712c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,308 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "csv"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "moze_analyzer"
+version = "0.1.0"
+dependencies = [
+ "colored",
+ "console",
+ "csv",
+ "itertools",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "serde"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.203"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..2091015
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "moze_analyzer"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+colored = "2.1.0"
+console = "0.15.8"
+csv = "1.3.0"
+itertools = "0.13.0"
diff --git a/src/.drawing.rs.swp b/src/.drawing.rs.swp
new file mode 100644
index 0000000..e2f78ef
--- /dev/null
+++ b/src/.drawing.rs.swp
Binary files differ
diff --git a/src/.lib.rs.swp b/src/.lib.rs.swp
new file mode 100644
index 0000000..f230d70
--- /dev/null
+++ b/src/.lib.rs.swp
Binary files differ
diff --git a/src/drawing.rs b/src/drawing.rs
new file mode 100644
index 0000000..f65ca8f
--- /dev/null
+++ b/src/drawing.rs
@@ -0,0 +1,77 @@
+use console::Term;
+use colored::Colorize;
+use std::io::{self, Write};
+
+pub enum Category<'a> {
+ Title(&'a str),
+ Line(&'a str),
+ Wrong(&'a str),
+ Menu,
+ Clear,
+}
+
+pub fn ui(is: Category) -> char {
+ let term = Term::stdout;
+ let (_, cols) = term().size();
+
+ match is {
+ Category::Title(s) => drawing_title(&s, cols),
+ Category::Line(s) => drawing_line(&s),
+ Category::Wrong(s) => drawing_error(&s, cols),
+ Category::Menu => return drawing_menu(),
+ Category::Clear => clear_screen(),
+ }
+
+ // return randomly if pattern is not menu
+ return 'a'
+}
+
+fn drawing_menu() -> char {
+ let term = Term::stdout;
+
+ ui(Category::Title("MOZE Analyzer"));
+
+ println!("a. print summary by project\t\tb. print summary by time");
+ println!("q. quit");
+ print!("\nEnter your option: ");
+ io::stdout().flush().unwrap();
+ let option = term().read_char().unwrap();
+
+ println!("{}\n", String::from(option).blue());
+
+ option
+}
+
+fn drawing_title(s: &str, cols: u16) {
+ println!("");
+
+ for _ in 0..cols {
+ print!("-");
+ }
+ println!("");
+ for _ in 0..((cols - s.len() as u16) / 2) {
+ print!(" ");
+ }
+
+ println!("{}", s.green());
+ for _ in 0..cols {
+ print!("-");
+ }
+ println!("");
+}
+
+fn drawing_line(s: &str) {
+ println!("{s}");
+}
+
+fn drawing_error(s: &str, cols: u16) {
+ for _ in 0..((cols - s.len() as u16) / 2) {
+ print!(" ");
+ }
+ println!("{}",s.red());
+}
+
+fn clear_screen() {
+ let term = Term::stdout;
+ let _ = term().clear_screen();
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..a3a3522
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,144 @@
+use std::{
+ env,
+ error,
+ fs::{File},
+ io::BufReader,
+ collections::HashMap,
+};
+
+use itertools::Itertools;
+
+mod record;
+mod drawing;
+
+use record::Record;
+
+pub fn run() -> Result<(), Box<dyn error::Error>> {
+ let records = load_contents()?;
+
+ drawing::ui(drawing::Category::Clear);
+
+ loop {
+ match drawing::ui(drawing::Category::Menu) {
+ 'a' | 'A' => print_summary_by_project(&records),
+ 'b' | 'B' => print_summary_by_time(&records),
+ 'q' | 'Q' => {
+ drawing::ui(drawing::Category::Title("quit program"));
+ break;
+ },
+ _ => {
+ drawing::ui(drawing::Category::Wrong("Unsupported Option, please try again.\n"));
+ continue;
+ },
+ }
+ }
+
+ Ok(())
+}
+
+fn print_summary_by_time(records: &Vec<Record>) {
+ println!("placeholder");
+}
+
+fn print_summary_by_project(records: &Vec<Record>) {
+ let mut expenses: HashMap<String, HashMap<String, f64>> = HashMap::new();
+
+ let mut sum = HashMap::new();
+
+ /* group the project with separate currency */
+ for r in records.iter() {
+ /* exclude transfer and other type */
+ match r.record_type() {
+ "Expense" | "Fee" | "Refund" | "Income" => {
+ /* No project name will be ignore */
+ if let Some(project) = r.project() {
+ if project.is_empty() {
+ continue;
+ }
+
+ /* group projects with separate currency */
+ expenses
+ .entry(project)
+ .and_modify(|expense: &mut HashMap<String, f64>| {
+ expense
+ .entry(r.currency())
+ .and_modify(|v| *v += r.cal())
+ .or_insert(r.cal());
+ })
+ .or_insert(HashMap::from([(r.currency(), r.cal())]));
+
+ /* sum same project and currency amount */
+ let element = sum.entry(r.currency()).or_insert(0.0 as f64);
+ *element += r.cal();
+ }
+ },
+ _ => continue,
+ }
+ }
+
+ // print output
+ println!("print summary by project:");
+ for project in expenses.keys().sorted() {
+ println!("\n\t{project}");
+
+ print!("\t\t\t|");
+
+ for currency in expenses[project].keys().sorted() {
+ let expense = expenses[project][currency];
+ print!("\t{currency} {:.2}\t|", expense);
+ }
+ println!("");
+ }
+
+ println!("\n\n\tSummary");
+ print!("\t\t\t|");
+ for currency in sum.keys().sorted() {
+ print!("\t{currency} {:.2}\t|", sum[currency]);
+ }
+
+ println!("");
+}
+
+fn load_contents() -> Result<Vec<Record>, Box<dyn error::Error>> {
+ let args = env::args().skip(1).collect::<Vec<String>>();
+
+ let csv_file = check_csv(&args)?;
+
+ let mut reader = build_reader(csv_file)?;
+
+ let mut records = Vec::new();
+
+ for r in reader.records() {
+ let r = r.unwrap();
+ records.push(Record::new(r));
+ }
+ Ok(records)
+}
+
+fn build_reader(file_name: String)
+ -> Result<csv::Reader<BufReader<File>>, Box<dyn error::Error>>
+{
+ let file = File::open(file_name)?;
+
+ let buf = BufReader::new(file);
+
+ let ret = csv::ReaderBuilder::new()
+ .flexible(true)
+ .from_reader(buf);
+
+ Ok(ret)
+}
+
+fn check_csv(args: &Vec<String>) -> Result<String, Box<dyn error::Error>> {
+ let n = args.len();
+
+ if n != 1 {
+ return Err(Box::from("Only 1 argument required\n\nUsage: moze_analyzer [csv_file]"));
+ }
+
+ if args[0].contains(".csv") {
+ Ok(args[0].clone())
+ } else {
+ Err(Box::from("Not csv file"))
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..91b2f5b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,10 @@
+use moze_analyzer;
+use std::process;
+
+fn main() {
+ if let Err(err) = moze_analyzer::run() {
+ eprintln!("{err}");
+ process::exit(1);
+ }
+}
+
diff --git a/src/record.rs b/src/record.rs
new file mode 100644
index 0000000..841855e
--- /dev/null
+++ b/src/record.rs
@@ -0,0 +1,72 @@
+#[derive(Debug)]
+pub struct Record {
+ account: String,
+ currency: String,
+ record_type: String,
+ main_type: String,
+ subcategory: String,
+ price: f64,
+ fee: f64,
+ bonus: f64,
+ name: String,
+ store: Option<String>,
+ date: Date,
+ time: Option<String>,
+ project: Option<String>,
+ note: Option<String>,
+ tags: Option<String>,
+ target: Option<String>,
+}
+
+#[derive(Debug)]
+struct Date {
+ month: i32,
+ day: i32,
+ year: i32,
+}
+
+impl Record {
+ pub fn new(r: csv::StringRecord) -> Self {
+ Record {
+ account: r[0].to_string(),
+ currency: r[1].to_string(),
+ record_type: r[2].to_string(),
+ main_type: r[3].to_string(),
+ subcategory: r[4].to_string(),
+ price: r[5].parse().unwrap(),
+ fee: r[6].parse().unwrap(),
+ bonus: r[7].parse().unwrap(),
+ name: r[8].to_string(),
+ store: r.get(9).map(|s| s.to_string()),
+ date: {
+ let d = r[10].split('/').collect::<Vec<&str>>();
+ Date {
+ month: d[0].parse().unwrap(),
+ day: d[1].parse().unwrap(),
+ year: d[2].parse().unwrap(),
+ }
+ },
+ time: r.get(11).map(|s| s.to_string()),
+ project: r.get(12).map(|s| s.to_string()),
+ note: r.get(13).map(|s| s.to_string()),
+ tags: r.get(14).map(|s| s.to_string()),
+ target: r.get(15).map(|s| s.to_string()),
+ }
+ }
+
+ pub fn cal(&self) -> f64 {
+ self.price + self.fee + self.bonus
+ }
+
+ pub fn record_type(&self) -> &str {
+ self.record_type.as_str()
+ }
+
+ pub fn project(&self) -> Option<String> {
+ self.project.clone()
+ }
+
+ pub fn currency(&self) -> String {
+ self.currency.clone()
+ }
+}