Initial commit
This commit is contained in:
commit
95516d9405
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
.~*
|
||||
*.ods
|
1489
Cargo.lock
generated
Normal file
1489
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "bom-combine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
spreadsheet-ods = "0.12"
|
||||
csv = "1.2"
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
log = "0.4"
|
||||
stderrlog = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
icu_locid = "1.2"
|
179
src/main.rs
Normal file
179
src/main.rs
Normal file
@ -0,0 +1,179 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::eyre::Result;
|
||||
use icu_locid::locale;
|
||||
use log::{debug, error, info};
|
||||
use serde::Deserialize;
|
||||
use spreadsheet_ods::{
|
||||
formula::{fcellref, fcellrefa, fcellrefa_table},
|
||||
write_ods, CellStyle, CellStyleRef, Sheet, ValueFormatNumber, WorkBook,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[arg(required = true)]
|
||||
input: Vec<String>,
|
||||
output: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Record {
|
||||
#[serde(rename(deserialize = "Reference"))]
|
||||
reference: String,
|
||||
#[serde(rename(deserialize = "Qty"))]
|
||||
qty: u64,
|
||||
#[serde(rename(deserialize = "Value"))]
|
||||
value: String,
|
||||
#[serde(rename(deserialize = "Footprint"))]
|
||||
footprint: String,
|
||||
#[serde(rename(deserialize = "WE Part"))]
|
||||
we_part: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Bom {
|
||||
name: String,
|
||||
entries: Vec<Record>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.verbosity(3)
|
||||
.init()?;
|
||||
let args = Args::parse();
|
||||
|
||||
let mut boms = vec![];
|
||||
|
||||
for path in args.input {
|
||||
let path = Path::new(&path);
|
||||
if !path.exists() {
|
||||
error!("{:?} does not exist", path);
|
||||
continue;
|
||||
}
|
||||
info!("Opening {:?} as input", path);
|
||||
let mut reader = csv::Reader::from_path(path)?;
|
||||
let bom = Bom {
|
||||
name: path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||
entries: reader.deserialize().collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
boms.push(bom);
|
||||
}
|
||||
let mut wb = WorkBook::new(locale!("en_US"));
|
||||
|
||||
let mut summary_sheet = Sheet::new("Summary");
|
||||
summary_sheet.set_value(0, 0, "#");
|
||||
summary_sheet.set_value(0, 1, "Qty");
|
||||
summary_sheet.set_value(0, 2, "Value");
|
||||
summary_sheet.set_value(0, 3, "Footprint");
|
||||
summary_sheet.set_value(0, 4, "WE Part");
|
||||
summary_sheet.set_value(0, 5, "Price per Unit");
|
||||
summary_sheet.set_value(0, 6, "Total Price");
|
||||
summary_sheet.set_value(0, 7, "Link");
|
||||
summary_sheet.set_value(0, 10, "Name");
|
||||
summary_sheet.set_value(0, 11, "Amount");
|
||||
wb.push_sheet(summary_sheet);
|
||||
|
||||
let mut parts: Vec<(_, _, Option<_>, _)> = vec![];
|
||||
|
||||
for bom in boms {
|
||||
info!("Coppying {} to output file", bom.name);
|
||||
let mut sheet = Sheet::new(&bom.name);
|
||||
sheet.set_value(0, 0, "#");
|
||||
sheet.set_value(0, 1, "Reference");
|
||||
sheet.set_value(0, 2, "Qty");
|
||||
sheet.set_value(0, 3, "Value");
|
||||
sheet.set_value(0, 4, "Footprint");
|
||||
sheet.set_value(0, 5, "WE Part");
|
||||
|
||||
for (i, entry) in bom.entries.iter().enumerate() {
|
||||
let row = u32::try_from(i)? + 1;
|
||||
sheet.set_value(row, 0, row);
|
||||
sheet.set_value(row, 1, &entry.reference);
|
||||
sheet.set_value(row, 2, entry.qty);
|
||||
sheet.set_value(row, 3, &entry.value);
|
||||
sheet.set_value(row, 4, &entry.footprint);
|
||||
if let Some(we_part) = entry.we_part.to_owned() {
|
||||
sheet.set_value(row, 5, we_part);
|
||||
}
|
||||
let Some((_, _, _, refs)) = parts.iter_mut().find(|(value, footprint, we_part, _)| {
|
||||
value == &entry.value
|
||||
&& footprint == &entry.footprint
|
||||
&& (we_part.is_none() || entry.we_part.is_none() || *we_part == entry.we_part)
|
||||
}) else {
|
||||
parts.push((entry.value.to_owned(), entry.footprint.to_owned(), entry.we_part.to_owned(), vec![(bom.name.to_owned(), row)]));
|
||||
continue;
|
||||
};
|
||||
refs.push((bom.name.to_owned(), row));
|
||||
}
|
||||
|
||||
wb.push_sheet(sheet);
|
||||
}
|
||||
|
||||
info!("Sorting parts");
|
||||
parts.sort_unstable_by(
|
||||
|(value_a, footprint_a, _, _), (value_b, footprint_b, _, _)| match footprint_a
|
||||
.cmp(footprint_b)
|
||||
{
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Equal => value_a.cmp(value_b),
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
);
|
||||
|
||||
info!("Creating summary sheet");
|
||||
let mut boards = vec![];
|
||||
let summary_sheet = wb.sheet_mut(0);
|
||||
for (i, (value, footprint, we_part, refs)) in parts.iter().enumerate() {
|
||||
let row = u32::try_from(i)? + 1;
|
||||
summary_sheet.set_value(row, 0, row);
|
||||
summary_sheet.set_value(row, 2, value);
|
||||
summary_sheet.set_value(row, 3, footprint);
|
||||
if let Some(we_part) = we_part {
|
||||
summary_sheet.set_value(row, 4, we_part);
|
||||
}
|
||||
summary_sheet.set_formula(row, 6, format!("{}*{}", fcellref(row, 5), fcellref(row, 1)));
|
||||
|
||||
let mut qty_formula = "".to_owned();
|
||||
for (board, row) in refs {
|
||||
let board_index = boards
|
||||
.iter()
|
||||
.find_map(|(b, i)| if *b == board { Some(*i) } else { None })
|
||||
.unwrap_or_else(|| {
|
||||
let index = u32::try_from(boards.len()).unwrap() + 1;
|
||||
boards.push((board, index));
|
||||
summary_sheet.set_value(index, 10, board);
|
||||
summary_sheet.set_value(index, 11, 1);
|
||||
index
|
||||
});
|
||||
if qty_formula == "" {
|
||||
qty_formula = format!(
|
||||
"{}*{}",
|
||||
fcellrefa_table(board, *row, 2),
|
||||
fcellrefa(board_index, 11)
|
||||
);
|
||||
} else {
|
||||
qty_formula.push_str(
|
||||
format!(
|
||||
"+{}*{}",
|
||||
fcellrefa_table(board, *row, 2),
|
||||
fcellrefa(board_index, 11)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
summary_sheet.set_formula(row, 1, qty_formula);
|
||||
}
|
||||
|
||||
info!("Saving to {:?}", args.output);
|
||||
write_ods(&mut wb, args.output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user