Compare commits

...

10 commits
1.0.0 ... main

5 changed files with 210 additions and 26 deletions

View file

@ -1,10 +1,11 @@
[package]
name = "example"
name = "statement-converter"
version = "0.1.0"
edition = "2021"
[dependencies]
pdf-extract = "0.9.0"
walkdir = "2.5.0"
colored = "3.0.0"
clap = "4.5.36"
csv = "1.3.1"

View file

@ -1,10 +1,19 @@
// Libraries
use clap::{Arg, ArgAction, Command};
// Enums
pub enum ArgModes {
Convert,
Package,
}
// Structures
pub struct Arguments {
pub mode: ArgModes,
pub file_input: String,
pub file_output: String,
pub csv_directory: String,
}
// Implementations
@ -15,36 +24,98 @@ impl Arguments {
let matches = Command::new("statement-converter")
.about("A service that converts Bank Statement PDF files into CSV files")
.version("1.0.0")
.subcommand_required(true)
.arg_required_else_help(true)
/*
PDF Path
-- Convert Mode
*/
.arg(
Arg::new("file_in")
.short('i')
.long("file_in")
.help("The PDF you want to convert")
.action(ArgAction::Set)
.required(true),
.subcommand(
Command::new("convert")
.short_flag('c')
.long_flag("convert")
.about("Converts PDF Statements to CSV Files")
.arg(
Arg::new("file_in")
.id("file_in")
.help("The PDF you want to convert")
.action(ArgAction::Set)
.required(true),
)
.arg(
Arg::new("file_out")
.id("file_out")
.help("The CSV Path you want to save to")
.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),
.subcommand(
Command::new("package")
.short_flag('p')
.long_flag("package")
.about("Combines all CSV Files in a given directory into one MASSIVE one")
.arg(
Arg::new("directory")
.id("directory")
.help("The path where your CSV files are")
.action(ArgAction::Set)
.required(true),
)
.arg(
Arg::new("file_out")
.id("file_out")
.help("The path where the master CSV is saved")
.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();
// Getting required arguments
let mode: ArgModes;
let file_input: String;
let file_output: String;
let csv_directory: String;
// Getting the mode being run
match matches.subcommand() {
Some(("convert", convert_matches)) => {
mode = ArgModes::Convert;
file_input = convert_matches
.get_one::<String>("file_in")
.unwrap()
.to_owned();
file_output = convert_matches
.get_one::<String>("file_out")
.unwrap()
.to_owned();
csv_directory = String::new();
}
Some(("package", package_matches)) => {
mode = ArgModes::Package;
csv_directory = package_matches
.get_one::<String>("directory")
.unwrap()
.to_owned();
file_output = package_matches
.get_one::<String>("file_out")
.unwrap()
.to_owned();
file_input = String::new();
}
_ => unreachable!(),
}
// Returning a Result
Self {
mode,
file_input,
file_output,
csv_directory,
}
}
}

View file

@ -1,23 +1,22 @@
// Libraries
mod action;
mod args;
mod packager;
mod parser;
mod printer;
mod reader;
mod writer;
use args::Arguments;
use packager::Packager;
use parser::Parser;
use reader::Reader;
use writer::Writer;
use std::io::Result;
// Entry-Point
fn main() -> Result<()> {
// Reading the Arguments
let args = Arguments::new();
// Functions
fn convert(args: &Arguments) -> Result<()> {
// Display Status
printer::print_generic("📃", "Extracting Text");
@ -43,6 +42,31 @@ fn main() -> Result<()> {
printer::print_generic("🏁", "Successful Converting Job");
printer::print_generic("😎", "Thank you for using Statement Converter!");
// Ok!
Ok(())
}
fn package(args: &Arguments) -> Result<()> {
// Creating a Packager
let packager = Packager::new(args.csv_directory.clone(), args.file_output.clone());
// Starting it
packager.start()?;
// Ok!!
Ok(())
}
// Entry-Point
fn main() -> Result<()> {
// Reading the Arguments
let args = Arguments::new();
// What mode are we in?
match args.mode {
args::ArgModes::Convert => convert(&args)?,
args::ArgModes::Package => package(&args)?,
}
// It's ok!
Ok(())
}

88
project/src/packager.rs Normal file
View file

@ -0,0 +1,88 @@
// Libraries
use std::fs::File;
use std::io::Result;
use csv::{self, StringRecord};
use walkdir::WalkDir;
// Structures
pub struct Packager {
directory: String,
file_out: String,
}
// Implementations
impl Packager {
// Constructors
pub fn new(directory: String, file_out: String) -> Self {
Self {
directory,
file_out,
}
}
// Functions
fn read_csv(&self, path: &str) -> Result<Vec<StringRecord>> {
// Opening the File & Reading
let file = File::open(path)?;
let mut rdr = csv::Reader::from_reader(file);
// Creating a list of string records
let mut result: Vec<StringRecord> = Vec::new();
// Iterate through records
for record in rdr.records() {
result.push(record?);
}
// Ok!
Ok(result)
}
fn save(&self, records: Vec<StringRecord>) -> Result<()> {
// Writing to the path we want
let mut writer = csv::Writer::from_path(&self.file_out)?;
// Creating the Header
writer.write_record(&["Date", "Description", "Amount"])?;
// Adding all Records
for record in &records {
writer.write_record(record)?;
}
// Flushing!!
writer.flush()?;
// Ok!!
Ok(())
}
pub fn start(&self) -> Result<()> {
// Holding all Records
let mut records: Vec<StringRecord> = Vec::new();
// Using WalkDir to go through the Directory
for entry in WalkDir::new(&self.directory) {
// Reference the Path
let entry = entry?;
let path = entry.path();
// Is it a file?
if !path.is_file() {
continue;
}
// Reading the CSV from the Path
let file = self.read_csv(path.to_str().unwrap())?;
// Adding it to the record list
records.extend(file);
}
// Saving the Records
self.save(records)?;
// Ok!!
Ok(())
}
}

View file

@ -5,5 +5,5 @@ clear
docker run --rm -it \
-v "$PWD/project:/src:Z" \
-v "$PWD/build:/app:Z" \
-e "PROJ_NAME=example" \
-e "PROJ_NAME=statement-converter" \
rust-buildbot $@