add groupby

This commit is contained in:
JMARyA 2023-10-26 16:59:06 +02:00
parent 99fe4ecee1
commit 9fa39fc1c2
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
3 changed files with 112 additions and 30 deletions

View file

@ -24,6 +24,7 @@ pub fn get_args() -> ArgMatches {
.default_value("file.title:Title"), .default_value("file.title:Title"),
) )
.arg(arg!(-s --sortby <KEY> "Sort results based on specified key").required(false)) .arg(arg!(-s --sortby <KEY> "Sort results based on specified key").required(false))
.arg(arg!(-g --groupby <KEY> "Group results based on specified key").required(false))
.arg(arg!(-r --reverse "Reverse the results").required(false)) .arg(arg!(-r --reverse "Reverse the results").required(false))
.get_matches() .get_matches()
} }

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use txd::DataType; use txd::DataType;
/// get frontmatter from markdown document /// get frontmatter from markdown document
@ -79,16 +81,7 @@ impl Index {
/// Build a table with specified columns from index within specified scope /// Build a table with specified columns from index within specified scope
#[must_use] #[must_use]
pub fn select_columns( pub fn apply(&self, limit: usize, offset: usize, sort: Option<String>, reverse: bool) -> Self {
&self,
col: &[String],
limit: usize,
offset: usize,
sort: Option<String>,
reverse: bool,
) -> Table {
let mut rows = vec![];
let mut scope = self.documents.clone(); let mut scope = self.documents.clone();
if let Some(sort) = sort { if let Some(sort) = sort {
@ -112,7 +105,28 @@ impl Index {
scope.into_iter().take(limit).collect() scope.into_iter().take(limit).collect()
}; };
for doc in scope { Self { documents: scope }
}
#[must_use]
pub fn group_by(&self, key: &str) -> HashMap<String, Self> {
let mut grouped_items: HashMap<String, Vec<Document>> = HashMap::new();
for doc in self.documents.clone() {
grouped_items.entry(doc.get_key(key)).or_default().push(doc);
}
grouped_items
.into_iter()
.map(|(key, item)| (key, Index { documents: item }))
.collect()
}
#[must_use]
pub fn create_table_data(&self, col: &[String]) -> Table {
let mut rows = vec![];
for doc in &self.documents {
let mut rcol = vec![]; let mut rcol = vec![];
for c in col { for c in col {
rcol.push(doc.get_key(c)); rcol.push(doc.get_key(c));
@ -138,7 +152,11 @@ impl Index {
let mut a = txd::parse(&a_str); let mut a = txd::parse(&a_str);
let b = txd::parse(&f.2); let b = txd::parse(&f.2);
log::debug!("Trying to compare {a:?} and {b:?} with {:?}", f.1); log::debug!(
"Trying to compare '{}' = {a:?} and {b:?} with {:?}",
f.0,
f.1
);
if a_str.is_empty() { if a_str.is_empty() {
// TODO : Maybe add explicit null instead of empty string // TODO : Maybe add explicit null instead of empty string

View file

@ -1,10 +1,9 @@
use std::io::IsTerminal; use std::{collections::HashMap, io::IsTerminal};
use mdq::Index; use mdq::Index;
mod args; mod args;
// TODO : Add debug logging
// TODO : Add documentation comments // TODO : Add documentation comments
// TODO : Add tests // TODO : Add tests
// TODO : Add GROUP BY Function // TODO : Add GROUP BY Function
@ -21,7 +20,13 @@ fn main() {
let offset: usize = args.get_one::<String>("offset").unwrap().parse().unwrap(); let offset: usize = args.get_one::<String>("offset").unwrap().parse().unwrap();
let sort_by = args.get_one::<String>("sortby").map(|x| x.to_owned()); let sort_by = args
.get_one::<String>("sortby")
.map(std::borrow::ToOwned::to_owned);
let group_by = args
.get_one::<String>("groupby")
.map(std::borrow::ToOwned::to_owned);
let reversed = args.get_flag("reverse"); let reversed = args.get_flag("reverse");
@ -30,7 +35,7 @@ fn main() {
.unwrap() .unwrap()
.cloned() .cloned()
.collect(); .collect();
log::info!("selected columns: {columns:?}"); log::debug!("columns: {columns:?}");
let (columns, headers): (Vec<_>, Vec<_>) = columns let (columns, headers): (Vec<_>, Vec<_>) = columns
.into_iter() .into_iter()
@ -41,21 +46,71 @@ fn main() {
}) })
.unzip(); .unzip();
if columns != headers {
log::debug!("renamed headers: {headers:?}");
}
let filters = args let filters = args
.get_many::<String>("filter") .get_many::<String>("filter")
.map_or_else(std::vec::Vec::new, std::iter::Iterator::collect); .map_or_else(std::vec::Vec::new, std::iter::Iterator::collect);
log::debug!("raw filters: {filters:?}");
let filters: Vec<_> = filters let filters: Vec<_> = filters
.into_iter() .into_iter()
.map(|x| txd::filter::parse_condition(x).expect("failed to parse filter")) .map(|x| txd::filter::parse_condition(x).expect("failed to parse filter"))
.collect(); .collect();
log::debug!("parsed filters: {filters:?}");
let mut i = Index::new(root_dir); let mut i = Index::new(root_dir);
if !filters.is_empty() { if !filters.is_empty() {
i = i.filter_documents(&filters); i = i.filter_documents(&filters);
} }
let data = i.select_columns(&columns, limit, offset, sort_by, reversed); i = i.apply(limit, offset, sort_by, reversed);
if group_by.is_some() {
let grouped = i.group_by(&group_by.unwrap());
let grouped: HashMap<_, _> = grouped
.into_iter()
.map(|(key, val)| (key, val.create_table_data(&columns)))
.collect();
if output_json {
let mut data = serde_json::json!(
{
"columns": columns,
"results": grouped
}
);
if columns != headers {
data.as_object_mut()
.unwrap()
.insert("headers".into(), headers.into());
}
println!("{}", serde_json::to_string(&data).unwrap());
return;
}
if std::io::stdout().is_terminal() {
for (group, val) in grouped {
println!("# {group}");
print_result(val, &headers);
}
} else {
let mut first = true;
for (_, val) in grouped {
if first {
print_csv(val, Some(&headers));
first = false;
continue;
}
print_csv(val, None);
}
}
return;
}
let data = i.create_table_data(&columns);
if output_json { if output_json {
let mut data = serde_json::json!( let mut data = serde_json::json!(
@ -72,21 +127,15 @@ fn main() {
println!("{}", serde_json::to_string(&data).unwrap()); println!("{}", serde_json::to_string(&data).unwrap());
return; return;
} }
if std::io::stdout().is_terminal() {
if data.is_empty() { print_result(data, &headers);
return; } else {
print_csv(data, Some(&headers));
} }
}
if !std::io::stdout().is_terminal() { fn print_result(data: Vec<Vec<String>>, headers: &[String]) {
let mut writer = csv::WriterBuilder::new().from_writer(vec![]); if data.is_empty() {
writer.write_record(headers).unwrap();
for e in data {
writer.write_record(e).unwrap();
}
print!(
"{}",
String::from_utf8(writer.into_inner().unwrap()).unwrap()
);
return; return;
} }
@ -98,3 +147,17 @@ fn main() {
println!("{table}"); println!("{table}");
} }
fn print_csv(data: Vec<Vec<String>>, headers: Option<&[String]>) {
let mut writer = csv::WriterBuilder::new().from_writer(vec![]);
if let Some(headers) = headers {
writer.write_record(headers).unwrap();
}
for e in data {
writer.write_record(e).unwrap();
}
print!(
"{}",
String::from_utf8(writer.into_inner().unwrap()).unwrap()
);
}