CLI
This commit is contained in:
72
Cargo.lock
generated
72
Cargo.lock
generated
@@ -19,6 +19,26 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
@@ -77,6 +97,21 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codepage"
|
||||
version = "0.1.1"
|
||||
@@ -157,6 +192,15 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.109"
|
||||
@@ -285,6 +329,12 @@ version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.82"
|
||||
@@ -296,6 +346,15 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
@@ -327,6 +386,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
@@ -339,6 +404,12 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
@@ -379,6 +450,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"calamine",
|
||||
"chrono",
|
||||
"clap",
|
||||
"rusqlite",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
calamine = "0.18.0"
|
||||
chrono = "0.4.19"
|
||||
clap = { version = "2.34.0"}
|
||||
rusqlite = { version = "0.26.3", features = ["bundled"] }
|
||||
smallvec = "1.7.0"
|
||||
|
||||
|
||||
64
sql/get_top.sql
Normal file
64
sql/get_top.sql
Normal file
@@ -0,0 +1,64 @@
|
||||
SELECT
|
||||
description,
|
||||
sku,
|
||||
level2,
|
||||
level3,
|
||||
revenue,
|
||||
quantity,
|
||||
trend
|
||||
from
|
||||
(
|
||||
SELECT
|
||||
*,
|
||||
ROW_NUMBER () OVER (
|
||||
PARTITION BY level2
|
||||
ORDER BY
|
||||
SUM(revenue) DESC
|
||||
) rownum,
|
||||
SUM(revenue) OVER (PARTITION BY level2) total,
|
||||
ROUND(SUM(revenue), 2) as revenue,
|
||||
SUM(quantity) as quantity,
|
||||
trend
|
||||
FROM
|
||||
(
|
||||
select
|
||||
*,
|
||||
cast(past as float) / 3 as past,
|
||||
round(
|
||||
cast(present as float) /(cast(past as float) / 3.0),
|
||||
2
|
||||
) as trend
|
||||
from
|
||||
(
|
||||
select
|
||||
*,
|
||||
sum(quantity) FILTER (
|
||||
WHERE
|
||||
year = $year
|
||||
and month between $($month - 4)
|
||||
and $($month - 1)
|
||||
) over (PARTITION BY description) as past,
|
||||
sum(quantity) FILTER (
|
||||
WHERE
|
||||
year = $year
|
||||
and month = %[2]s
|
||||
) over (PARTITION BY description) as present
|
||||
from
|
||||
data
|
||||
)
|
||||
)
|
||||
where
|
||||
year = %[1]s
|
||||
and month = %[2]s
|
||||
GROUP BY
|
||||
description
|
||||
ORDER BY
|
||||
total DESC
|
||||
)
|
||||
WHERE
|
||||
rownum <= 10
|
||||
and revenue > 0
|
||||
order by
|
||||
(sum(revenue) over (
|
||||
partition by level2
|
||||
)) desc;
|
||||
137
src/converter/mod.rs
Normal file
137
src/converter/mod.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use std::{error::Error};
|
||||
use std::{collections::HashMap};
|
||||
|
||||
use rusqlite::{Connection, Result};
|
||||
use calamine::{open_workbook, Reader, Xlsx,DataType};
|
||||
|
||||
use super::types::Data;
|
||||
|
||||
const SCHEMA: &str = r#"CREATE TABLE IF NOT EXISTS data (
|
||||
description text,
|
||||
sku text,
|
||||
level2 text,
|
||||
level3 text,
|
||||
revenue float,
|
||||
cost float,
|
||||
quantity integer,
|
||||
year integer,
|
||||
month integer
|
||||
);
|
||||
"#;
|
||||
|
||||
pub fn convert_excel(path: &str) -> Result<(),Box<dyn Error>> {
|
||||
let r = get_excel(path)?;
|
||||
let (indexs,skip_count) = get_index(&r);
|
||||
let data_vec = parse_data(&r,indexs,skip_count)?;
|
||||
write_sql(&data_vec)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_excel(path: &str) -> Result<calamine::Range<DataType>, Box<dyn Error>> {
|
||||
let mut excel: Xlsx<_> = open_workbook(path)?;
|
||||
let r = excel
|
||||
.worksheet_range("Export")
|
||||
.expect("Is There")
|
||||
?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn get_index(r: &calamine::Range<DataType>) -> (HashMap<&'static str, usize>,usize) {
|
||||
let mut indexs = HashMap::new();
|
||||
let mut skip_count = 0;
|
||||
'outer: for row in r.rows() {
|
||||
skip_count += 1;
|
||||
for (i, elm) in row.iter().enumerate() {
|
||||
let header = elm.get_string().expect("header issue");
|
||||
match header {
|
||||
"FiscalYearMonth" => indexs.insert("date", i),
|
||||
"Level2" => indexs.insert("level2", i),
|
||||
"Level3" => indexs.insert("level3", i),
|
||||
"MaterialEntered" => indexs.insert("sku", i),
|
||||
"Quantity" => indexs.insert("quantity", i),
|
||||
"SalesRevenue" => indexs.insert("revenue", i),
|
||||
"CostOfGoodsSold" => indexs.insert("cost", i),
|
||||
"ProductDescription" => indexs.insert("description", i),
|
||||
_ => None,
|
||||
};
|
||||
if indexs.len() == 8 {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
(indexs,skip_count)
|
||||
}
|
||||
|
||||
fn parse_data(r: &calamine::Range<DataType>, indexs: HashMap<&str,usize>, skip_count: usize) -> Result<Vec<Data>,Box<dyn Error>> {
|
||||
let mut data_vec = Vec::with_capacity(100000);
|
||||
for row in r.rows().skip(skip_count) {
|
||||
if row[indexs["sku"]].get_string() == None {
|
||||
break;
|
||||
}
|
||||
let datestring = row[indexs["date"]].get_string().expect("Date Parsing Issue");
|
||||
let year = datestring[0..4].parse::<i64>()?;
|
||||
let month = datestring[5..].parse::<i64>()?;
|
||||
data_vec.push(Data {
|
||||
description: String::from(row[indexs["description"]].get_string().unwrap().replace("\"", "")),
|
||||
sku: String::from(row[indexs["sku"]].get_string().unwrap()),
|
||||
level2: String::from(row[indexs["level2"]].get_string().unwrap()),
|
||||
level3: String::from(row[indexs["level3"]].get_string().unwrap()),
|
||||
revenue: match row[indexs["revenue"]].get_float() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["revenue"]].get_int() {
|
||||
Some(x) => x as f64,
|
||||
None => 0.0,
|
||||
},
|
||||
},
|
||||
cost: match row[indexs["cost"]].get_float() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["cost"]].get_int() {
|
||||
Some(x) => x as f64,
|
||||
None => 0.0,
|
||||
},
|
||||
},
|
||||
quantity: match row[indexs["quantity"]].get_int() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["quantity"]].get_float() {
|
||||
Some(x) => x as i64,
|
||||
None => 0,
|
||||
},
|
||||
},
|
||||
year: year,
|
||||
month: month,
|
||||
});
|
||||
}
|
||||
Ok(data_vec)
|
||||
}
|
||||
|
||||
fn write_sql(data_vec: &Vec<Data>) -> Result<(), Box<dyn Error>> {
|
||||
let conn = Connection::open("data.db")?;
|
||||
conn.execute("DROP TABLE IF EXISTS data;", [])?;
|
||||
conn.execute(SCHEMA, [])?;
|
||||
|
||||
let mut inserts = Vec::with_capacity(100000);
|
||||
|
||||
inserts.push(String::from("INSERT INTO data (description,sku,level2,level3,revenue,cost,quantity,year,month) VALUES "));
|
||||
|
||||
for row in data_vec {
|
||||
let other: String = format!(
|
||||
"(\"{}\",\"{}\",\"{}\",\"{}\",{},{},{},{},{}),",
|
||||
row.description,
|
||||
row.sku,
|
||||
row.level2,
|
||||
row.level3,
|
||||
row.revenue,
|
||||
row.cost,
|
||||
row.quantity,
|
||||
row.year,
|
||||
row.month
|
||||
);
|
||||
inserts.push(other);
|
||||
}
|
||||
let mut ins = inserts.join("");
|
||||
ins.pop();
|
||||
ins.push(';');
|
||||
|
||||
conn.execute(&ins, [])?;
|
||||
Ok(())
|
||||
}
|
||||
171
src/main.rs
171
src/main.rs
@@ -1,135 +1,58 @@
|
||||
use std::{collections::HashMap, error::Error};
|
||||
use std::error::Error;
|
||||
|
||||
use calamine::{open_workbook, Reader, Xlsx};
|
||||
use rusqlite::{Connection, Result};
|
||||
mod converter;
|
||||
mod types;
|
||||
|
||||
const SCHEMA: &str = r#"CREATE TABLE IF NOT EXISTS data (
|
||||
description text,
|
||||
sku text,
|
||||
level2 text,
|
||||
level3 text,
|
||||
revenue float,
|
||||
cost float,
|
||||
quantity integer,
|
||||
year integer,
|
||||
month integer
|
||||
);
|
||||
"#;
|
||||
use clap::{App, Arg, SubCommand};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Data {
|
||||
description: String,
|
||||
sku: String,
|
||||
level2: String,
|
||||
level3: String,
|
||||
revenue: f64,
|
||||
cost: f64,
|
||||
quantity: i64,
|
||||
year: i64,
|
||||
month: i64,
|
||||
}
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let now = chrono::offset::Local::now();
|
||||
let mut data_vec = Vec::with_capacity(100000);
|
||||
let mut excel: Xlsx<_> = open_workbook("data.xlsx").unwrap();
|
||||
let r = excel
|
||||
.worksheet_range("Export")
|
||||
.expect("Is There")
|
||||
.expect("Excel Read Error");
|
||||
|
||||
let mut indexs = HashMap::new();
|
||||
let mut skip_count = 0;
|
||||
'outer: for row in r.rows() {
|
||||
skip_count += 1;
|
||||
for (i, elm) in row.iter().enumerate() {
|
||||
let header = elm.get_string().expect("header issue");
|
||||
match header {
|
||||
"FiscalYearMonth" => indexs.insert("date", i),
|
||||
"Level2" => indexs.insert("level2", i),
|
||||
"Level3" => indexs.insert("level3", i),
|
||||
"MaterialEntered" => indexs.insert("sku", i),
|
||||
"Quantity" => indexs.insert("quantity", i),
|
||||
"SalesRevenue" => indexs.insert("revenue", i),
|
||||
"CostOfGoodsSold" => indexs.insert("cost", i),
|
||||
"ProductDescription" => indexs.insert("description", i),
|
||||
_ => None,
|
||||
};
|
||||
if indexs.len() == 8 {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for row in r.rows().skip(skip_count) {
|
||||
if row[indexs["sku"]].get_string() == None {
|
||||
break;
|
||||
}
|
||||
let datestring = row[indexs["date"]].get_string().expect("Date Parsing Issue");
|
||||
let year = datestring[0..4].parse::<i64>().unwrap();
|
||||
let month = datestring[5..].parse::<i64>().unwrap();
|
||||
data_vec.push(Data {
|
||||
description: String::from(row[indexs["description"]].get_string().unwrap().replace("\"", "")),
|
||||
sku: String::from(row[indexs["sku"]].get_string().unwrap()),
|
||||
level2: String::from(row[indexs["level2"]].get_string().unwrap()),
|
||||
level3: String::from(row[indexs["level3"]].get_string().unwrap()),
|
||||
revenue: match row[indexs["revenue"]].get_float() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["revenue"]].get_int() {
|
||||
Some(x) => x as f64,
|
||||
None => 0.0,
|
||||
},
|
||||
},
|
||||
cost: match row[indexs["cost"]].get_float() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["cost"]].get_int() {
|
||||
Some(x) => x as f64,
|
||||
None => 0.0,
|
||||
},
|
||||
},
|
||||
quantity: match row[indexs["quantity"]].get_int() {
|
||||
Some(x) => x,
|
||||
None => match row[indexs["quantity"]].get_float() {
|
||||
Some(x) => x as i64,
|
||||
None => 0,
|
||||
},
|
||||
},
|
||||
year: year,
|
||||
month: month,
|
||||
});
|
||||
}
|
||||
|
||||
let conn = Connection::open("data.db")?;
|
||||
conn.execute("DROP TABLE IF EXISTS data;", [])?;
|
||||
conn.execute(SCHEMA, [])?;
|
||||
|
||||
let mut inserts = Vec::with_capacity(100000);
|
||||
|
||||
inserts.push(String::from("INSERT INTO data (description,sku,level2,level3,revenue,cost,quantity,year,month) VALUES "));
|
||||
|
||||
for row in data_vec {
|
||||
let other: String = format!(
|
||||
"(\"{}\",\"{}\",\"{}\",\"{}\",{},{},{},{},{}),",
|
||||
row.description,
|
||||
row.sku,
|
||||
row.level2,
|
||||
row.level3,
|
||||
row.revenue,
|
||||
row.cost,
|
||||
row.quantity,
|
||||
row.year,
|
||||
row.month
|
||||
let mut app = App::new("xlr")
|
||||
.arg(
|
||||
Arg::with_name("convert")
|
||||
.short("c")
|
||||
.long("convert")
|
||||
.value_name("FILE")
|
||||
.help("File to convert")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("get-top")
|
||||
.short("gt")
|
||||
.long("get-top")
|
||||
.value_names(&["YEAR", "MONTH"])
|
||||
.help("Year and month to get top info")
|
||||
.takes_value(true),
|
||||
);
|
||||
inserts.push(other);
|
||||
|
||||
let mut helpper = app.clone();
|
||||
let matches = app.get_matches();
|
||||
let mut did_run = false;
|
||||
|
||||
//Convert
|
||||
match matches.value_of("convert") {
|
||||
Some(val) => {
|
||||
let now = chrono::offset::Local::now();
|
||||
converter::convert_excel(val)?;
|
||||
println!(
|
||||
"{:?}",
|
||||
(chrono::offset::Local::now() - now).num_milliseconds()
|
||||
);
|
||||
did_run = true;
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
let mut ins = inserts.join("");
|
||||
ins.pop();
|
||||
ins.push(';');
|
||||
|
||||
conn.execute(&ins, [])?;
|
||||
//Get Top
|
||||
match matches.values_of("get-top") {
|
||||
Some(vals) => {
|
||||
did_run = true;
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
let after = chrono::offset::Local::now();
|
||||
let diff = after - now;
|
||||
println!("{:?}", diff.num_milliseconds());
|
||||
if !did_run {
|
||||
helpper.print_help().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
12
src/types/mod.rs
Normal file
12
src/types/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#[derive(Debug)]
|
||||
pub struct Data {
|
||||
pub description: String,
|
||||
pub sku: String,
|
||||
pub level2: String,
|
||||
pub level3: String,
|
||||
pub revenue: f64,
|
||||
pub cost: f64,
|
||||
pub quantity: i64,
|
||||
pub year: i64,
|
||||
pub month: i64,
|
||||
}
|
||||
Reference in New Issue
Block a user