Add core modules for statement parsing and CLI args

Includes initial implementations for action, args, parser, and reader
modules, supporting PDF reading and argument parsing.
This commit is contained in:
Maddox Werts 2025-04-18 15:12:34 -04:00
parent 5bddf93ec8
commit 92a666d9bc
4 changed files with 186 additions and 0 deletions

10
project/src/action.rs Normal file
View file

@ -0,0 +1,10 @@
// Libraries
// Structures
pub struct Transaction {
description: String,
date: String,
amount: f64,
}
// Implementations

50
project/src/args.rs Normal file
View file

@ -0,0 +1,50 @@
// Libraries
use clap::{Arg, ArgAction, Command};
// Structures
pub struct Arguments {
pub file_input: String,
pub file_output: String,
}
// Implementations
impl Arguments {
// Constructors
pub fn new() -> Self {
// Creating an Application
let matches = Command::new("statement-converter")
.about("A service that converts Bank Statement PDF files into CSV files")
.version("1.0.0")
.arg_required_else_help(true)
/*
PDF Path
*/
.arg(
Arg::new("file_in")
.short('i')
.long("file_in")
.help("The PDF you want to convert")
.action(ArgAction::Set)
.required(true),
)
.arg(
Arg::new("file_out")
.short('o')
.long("file_out")
.help("The CSV Path you want to save to")
.action(ArgAction::Set)
.required(true),
)
.get_matches();
// Getting the required arguments
let file_input = matches.get_one::<String>("file_in").unwrap().to_string();
let file_output = matches.get_one::<String>("file_out").unwrap().to_string();
// Returning a Result
Self {
file_input,
file_output,
}
}
}

95
project/src/parser.rs Normal file
View file

@ -0,0 +1,95 @@
// Libraries
use super::action::Transaction;
// Enums
enum ParserModes {
None,
Debit,
Credit,
}
// Structures
pub struct Parser {
pub transactions: Vec<Transaction>,
mode: ParserModes,
paused: bool,
content: String,
}
// Implementations
impl Parser {
// Constructors
pub fn new(content: String) -> Self {
// Return Result
Self {
transactions: Vec::new(),
mode: ParserModes::None,
paused: false,
content,
}
}
// Functions
fn none(&mut self, line: &str) {
// Checking if the line is the beginning of a table
if line == "Other withdrawals, debits and service charges" {
self.mode = ParserModes::Debit;
} else if line == "Deposits, credits and interest" {
self.mode = ParserModes::Credit;
}
}
fn debit(&mut self, line: &str) {
// Should be pause?
if line.contains("continued") {
self.paused = true;
return;
}
// Checking if it starts with a number
if !line.chars().next().map_or(false, |c| c.is_ascii_digit()) {
return;
}
// Print the line
println!("Debit: {}", line);
}
fn credit(&mut self, line: &str) {}
fn pause_check(&mut self, line: &str) {
// Check to see if the line contains "continued"
if !line.contains("continued") {
return;
}
// We're good to continue now
self.paused = false;
}
pub fn start(&mut self) {
// Referencing Content
let content = self.content.clone();
// Scanning Line-by-Line through Content
for line in content.split('\n') {
// Empty line?
if line.is_empty() {
continue;
}
// Are we paused?
if self.paused {
self.pause_check(line);
continue;
}
// Switching based on Mode
match self.mode {
ParserModes::None => self.none(line),
ParserModes::Debit => self.debit(line),
ParserModes::Credit => self.credit(line),
}
}
}
}

31
project/src/reader.rs Normal file
View file

@ -0,0 +1,31 @@
// Libraries
use std::fs::read;
use std::io::Result;
use pdf_extract;
// Structures
pub struct Reader {
bytes: Vec<u8>,
}
// Implementations
impl Reader {
// Constructors
pub fn new(path: &String) -> Result<Self> {
// Reading the file from the path
let bytes = read(path)?;
// Returning the Result
Ok(Self { bytes })
}
// Functions
pub fn extract(&self) -> String {
// Getting the text from our PDF Bytes
let text = pdf_extract::extract_text_from_mem(&self.bytes).unwrap();
// It's Ok!
text
}
}