1use crate::{FactSet, Node, NodeKind, Span};
11
12pub trait Extractor {
14 fn extract(&self, path: &str, blob_id: &str, bytes: &[u8]) -> FactSet;
19}
20
21#[derive(Debug, Clone, Copy, Default)]
25pub struct FileNodeExtractor;
26
27impl Extractor for FileNodeExtractor {
28 fn extract(&self, path: &str, blob_id: &str, bytes: &[u8]) -> FactSet {
29 let name = path.rsplit('/').next().unwrap_or(path).to_owned();
30 let lines = bytes
31 .iter()
32 .fold(0usize, |n, &b| n + usize::from(b == b'\n'));
33 let end = u32::try_from(bytes.len()).unwrap_or(u32::MAX);
34 let node = Node {
35 key: format!("file:{path}"),
36 kind: NodeKind::File,
37 name,
38 path: Some(path.to_owned()),
39 lang: None,
40 blob_hash: Some(blob_id.to_owned()),
41 span: Some(Span::new(0, end)),
42 meta: serde_json::json!({ "bytes": bytes.len(), "lines": lines }),
43 };
44 FactSet::new().with_node(node)
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::{Extractor, FileNodeExtractor};
51 use crate::NodeKind;
52
53 #[test]
54 fn file_node_extractor_is_deterministic_and_tagged() {
55 let ex = FileNodeExtractor;
56 let a = ex.extract("src/lib.rs", "abc123", b"one\ntwo\n");
57 let b = ex.extract("src/lib.rs", "abc123", b"one\ntwo\n");
58 assert_eq!(a, b, "extraction must be deterministic");
59
60 assert_eq!(a.nodes.len(), 1);
61 assert!(a.edges.is_empty());
62 let node = &a.nodes[0];
63 assert_eq!(node.key, "file:src/lib.rs");
64 assert_eq!(node.kind, NodeKind::File);
65 assert_eq!(node.name, "lib.rs");
66 assert_eq!(node.blob_hash.as_deref(), Some("abc123"));
67 assert_eq!(node.meta["lines"], 2);
68 assert_eq!(node.meta["bytes"], 8);
69 }
70}