use crate::db::GraphDb;
use crate::Direction;
use core_storage::fs::Fs;
use core_storage::Value;
pub(super) fn str_prop<F: Fs>(db: &GraphDb<F>, key: &str, field: &str) -> Option<String> {
match db.node_ref(key).and_then(|n| n.prop(field)) {
Some(Value::Str(s)) => Some(s),
_ => None,
}
}
pub(super) fn int_prop<F: Fs>(db: &GraphDb<F>, key: &str, field: &str) -> Option<i64> {
match db.node_ref(key).and_then(|n| n.prop(field)) {
Some(Value::Int(i)) => Some(i),
_ => None,
}
}
pub(super) fn str_list(v: Option<Value>) -> Vec<String> {
match v {
Some(Value::List(items)) => items
.into_iter()
.filter_map(|i| match i {
Value::Str(s) => Some(s),
_ => None,
})
.collect(),
_ => Vec::new(),
}
}
pub(super) fn list_prop<F: Fs>(db: &GraphDb<F>, key: &str, field: &str) -> Vec<String> {
str_list(db.node_ref(key).and_then(|n| n.prop(field)))
}
pub(super) fn label_of<F: Fs>(db: &GraphDb<F>, key: &str) -> Option<String> {
db.node_ref(key).map(|n| n.label().to_string())
}
pub(super) fn rank<T: PartialOrd + Copy>(items: &mut [(String, T)]) {
items.sort_by(|a, b| {
b.1.partial_cmp(&a.1)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.0.cmp(&b.0))
});
}
pub(super) fn neighbors<F: Fs>(
db: &GraphDb<F>,
key: &str,
edge_type: &str,
dir: Direction,
) -> Vec<String> {
let mut out = db.neighbors(key, edge_type, dir).unwrap_or_default();
out.sort();
out.dedup();
out
}
pub(super) fn neighbors_both<F: Fs>(db: &GraphDb<F>, key: &str, edge_type: &str) -> Vec<String> {
let mut out = neighbors(db, key, edge_type, Direction::Out);
out.extend(neighbors(db, key, edge_type, Direction::In));
out.sort();
out.dedup();
out
}
pub(super) fn score_of<F: Fs>(db: &GraphDb<F>, edge_type: &str, a: &str, b: &str) -> Option<f64> {
let read = |src: &str, dst: &str| match db.get_edge_prop(edge_type, src, dst, "score") {
Some(Value::Float(f)) => Some(f),
Some(Value::Int(i)) => Some(i as f64),
_ => None,
};
read(a, b).or_else(|| read(b, a))
}
pub(super) fn author_name<F: Fs>(db: &GraphDb<F>, key: &str) -> String {
str_prop(db, key, "name").unwrap_or_else(|| key.to_string())
}
pub(super) fn owner_key<F: Fs>(db: &GraphDb<F>, file: &str) -> Option<String> {
neighbors(db, file, "TOP_AUTHOR", Direction::Out)
.into_iter()
.next()
}
pub(super) fn owner_name<F: Fs>(db: &GraphDb<F>, file: &str) -> Option<String> {
owner_key(db, file).map(|k| author_name(db, &k))
}
pub(super) fn symbol_file<F: Fs>(db: &GraphDb<F>, symbol: &str) -> Option<String> {
str_prop(db, symbol, "file_id").or_else(|| str_prop(db, symbol, "path"))
}
pub(super) fn evidence_line(entries: &[String], target: &str) -> Option<u32> {
entries.iter().find_map(|e| {
let (key, line) = e.split_once('\t')?;
(key == target).then(|| line.parse().ok())?
})
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct CommitFact {
pub sha: String,
pub ts: i64,
pub subject: String,
}
pub(super) fn commit_fact<F: Fs>(db: &GraphDb<F>, sha: &str) -> Option<CommitFact> {
let ts = int_prop(db, sha, "ts")?;
let message = str_prop(db, sha, "message").unwrap_or_default();
Some(CommitFact {
sha: sha.to_string(),
ts,
subject: message.lines().next().unwrap_or_default().to_string(),
})
}
pub(super) fn commits_of<F: Fs>(db: &GraphDb<F>, file: &str) -> Vec<CommitFact> {
let mut out: Vec<CommitFact> = list_prop(db, file, "commits")
.iter()
.filter_map(|sha| commit_fact(db, sha))
.collect();
out.sort_by(|a, b| b.ts.cmp(&a.ts).then(a.sha.cmp(&b.sha)));
out.dedup_by(|a, b| a.sha == b.sha);
out
}
pub(super) fn newest_commit_ts<F: Fs>(db: &GraphDb<F>) -> Option<i64> {
db.nodes_with_label("Commit")
.iter()
.filter_map(|n| match n.prop("ts") {
Some(Value::Int(ts)) => Some(ts),
_ => None,
})
.max()
}
pub(super) fn author_counts<F: Fs>(db: &GraphDb<F>, file: &str) -> Vec<(String, usize)> {
list_prop(db, file, "author_counts")
.iter()
.filter_map(|e| {
let (key, n) = e.rsplit_once('\t')?;
let n = n.parse().ok()?;
(!key.is_empty()).then(|| (key.to_string(), n))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_evidence_entry_yields_its_line_and_nothing_else() {
let entries = vec![
"src/a.rs\t12".to_string(),
"src/b.rs\tnot-a-line".to_string(),
"src/c.rs".to_string(),
];
assert_eq!(evidence_line(&entries, "src/a.rs"), Some(12));
assert_eq!(evidence_line(&entries, "src/b.rs"), None);
assert_eq!(evidence_line(&entries, "src/c.rs"), None);
assert_eq!(evidence_line(&entries, "src/d.rs"), None);
assert_eq!(evidence_line(&[], "src/a.rs"), None);
}
#[test]
fn rank_puts_the_biggest_first_and_breaks_ties_on_the_key() {
let mut items = vec![
("b".to_string(), 1.0),
("a".to_string(), 1.0),
("c".to_string(), 2.0),
];
rank(&mut items);
let keys: Vec<&str> = items.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["c", "a", "b"]);
}
}