This commit is contained in:
JMARyA 2023-10-25 16:33:12 +02:00
commit d12637ce75
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
7 changed files with 837 additions and 0 deletions

16
src/args.rs Normal file
View file

@ -0,0 +1,16 @@
use clap::{arg, command, ArgMatches};
pub fn get_args() -> ArgMatches {
command!()
.about("Query markdown files")
.arg(arg!([dir] "Directory to scan").required(true))
.arg(arg!(-j --json "Output result as JSON").required(false))
.arg(arg!(-l --limit <LIMIT> "Limit number of results returned").required(false))
.arg(arg!(-f --filter <FILTER>... "Filter to apply to the documents").required(false))
.arg(
arg!(-c --column <COLUMN>... "Specify output columns")
.required(false)
.default_value("path"),
)
.get_matches()
}

115
src/lib.rs Normal file
View file

@ -0,0 +1,115 @@
/// get frontmatter from markdown document
#[must_use]
pub fn get_frontmatter(markdown: &str) -> Option<String> {
let frontmatter_regex = regex::Regex::new(r"(?s)^---\s*\n(.*?)\n---").unwrap();
if let Some(captures) = frontmatter_regex.captures(markdown) {
let frontmatter = captures.get(1).map(|m| m.as_str().to_string());
frontmatter
} else {
None
}
}
#[derive(Debug)]
pub struct Document {
pub path: String,
pub frontmatter: serde_yaml::Value,
}
#[derive(Debug)]
pub struct Index {
pub documents: Vec<Document>,
}
/// Create a markdown document index over `dir`
pub fn scan_dir(dir: &str) -> Index {
let mut i = Index { documents: vec![] };
for e in walkdir::WalkDir::new(dir)
.into_iter()
.filter_map(std::result::Result::ok)
{
if e.path().is_dir() {
continue;
}
if e.path().extension().is_none() {
continue;
}
if e.path().extension().unwrap().to_str().unwrap() == "md" {
let path = e.path().to_str().unwrap().to_owned();
let content = std::fs::read_to_string(&path).unwrap();
let frontmatter = get_frontmatter(&content);
if let Some(frontmatter) = frontmatter {
let frontmatter = serde_yaml::from_str(&frontmatter).unwrap();
let doc = Document { path, frontmatter };
i.documents.push(doc);
} else {
i.documents.push(Document {
path,
frontmatter: serde_yaml::to_value(&serde_yaml::Mapping::new()).unwrap(),
});
}
}
}
i
}
/// Get a key from document.
/// This will return internal properties first, then it will search the document frontmatter for the key and return it. If nothing was found an empty string is returned.
fn get_key(d: &Document, key: &str) -> String {
if key == "file.title" {
let path = std::path::Path::new(&d.path);
return path.file_stem().unwrap().to_str().unwrap().to_string();
}
if let Some(val) = d.frontmatter.as_mapping().unwrap().get(key) {
// TODO : Fix format
format!("{val:?}")
} else {
String::new()
}
}
type Table = Vec<Vec<String>>;
/// Build a table with specified columns from index
#[must_use]
pub fn select_columns(i: &Index, col: &[&String]) -> Table {
let mut rows = vec![];
for doc in &i.documents {
let mut rcol = vec![];
for c in col {
rcol.push(get_key(doc, c));
}
rows.push(rcol);
}
rows
}
/// Apply filters to the documents of the index returning a new filtered index
#[must_use]
pub fn filter_documents(i: Index, filters: &[txd::filter::Filter]) -> Index {
let docs: Vec<_> = i
.documents
.into_iter()
.filter_map(|x| {
for f in filters {
let a = get_key(&x, &f.0);
let a = txd::parse(&a);
let b = txd::parse(&f.2);
if a.compare(f.1, b) {
return Some(x);
}
}
None
})
.collect();
Index { documents: docs }
}

77
src/main.rs Normal file
View file

@ -0,0 +1,77 @@
use mdq::{filter_documents, scan_dir, select_columns};
use prettytable::{Cell, Row};
mod args;
fn main() {
let args = args::get_args();
//println!("{args:?}");
let root_dir = args.get_one::<String>("dir").unwrap();
let output_json = args.get_flag("json");
let limit: Option<usize> = if let Some(limit_arg) = args.get_one::<String>("limit") {
limit_arg.parse().ok()
} else {
None
};
let columns: Vec<_> = if let Some(columns) = args.get_many::<String>("column") {
columns.collect()
} else {
vec![]
};
let filters: Vec<_> = if let Some(filters) = args.get_many::<String>("filter") {
filters.collect()
} else {
vec![]
};
let filters: Vec<_> = filters
.into_iter()
.map(|x| txd::filter::parse_condition(x).expect("failed to parse filter"))
.collect();
let mut i = scan_dir(root_dir);
if !filters.is_empty() {
i = filter_documents(i, &filters);
}
let data = if let Some(limit) = limit {
select_columns(&i, &columns.clone())
.into_iter()
.take(limit)
.collect::<Vec<_>>()
} else {
select_columns(&i, &columns.clone())
};
if output_json {
let data = serde_json::json!(
{
"columns": columns,
"results": data
}
);
println!("{}", serde_json::to_string(&data).unwrap());
return;
}
let mut table = prettytable::Table::new();
let headers = columns
.iter()
.map(|cell| Cell::new(cell))
.collect::<Vec<Cell>>();
table.set_titles(Row::new(headers));
// Add rows to the table
for row in data {
let cells: Vec<Cell> = row.iter().map(|cell| Cell::new(cell)).collect();
table.add_row(Row::new(cells));
}
// Print the table to the console
table.printstd();
}