init
This commit is contained in:
commit
d12637ce75
7 changed files with 837 additions and 0 deletions
16
src/args.rs
Normal file
16
src/args.rs
Normal 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
115
src/lib.rs
Normal 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
77
src/main.rs
Normal 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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue