Skip to main content

sinter_store/
scope.rs

1//! The one place the per-node scope rule lives: a node's own override, else
2//! its file node's override (generated banners), else the persisted file
3//! scope, else conservative path classification.
4
5use std::collections::HashMap;
6
7use redb::ReadableTable;
8use sinter_core::{CorpusScope, Node};
9
10use crate::error::StoreError;
11use crate::store::{FILE_SCOPE, NODE_SCOPE, Store};
12
13pub(crate) fn resolve(
14    node_scope: impl Fn(&str) -> Option<CorpusScope>,
15    file_scope: Option<CorpusScope>,
16    id: &str,
17    file: &str,
18) -> CorpusScope {
19    node_scope(id)
20        .or_else(|| node_scope(file))
21        .or(file_scope)
22        .unwrap_or_else(|| CorpusScope::classify_path(file))
23}
24
25/// Read-side scope lookup loaded once per command.
26#[derive(Debug, Default, Clone)]
27pub struct ScopeIndex {
28    files: HashMap<String, CorpusScope>,
29    nodes: HashMap<String, CorpusScope>,
30}
31
32impl ScopeIndex {
33    pub fn scope_of(&self, node: &Node) -> CorpusScope {
34        self.scope_of_id(node.id.as_str(), &node.file)
35    }
36
37    pub fn scope_of_id(&self, id: &str, file: &str) -> CorpusScope {
38        resolve(
39            |key| self.nodes.get(key).copied(),
40            self.files.get(file).copied(),
41            id,
42            file,
43        )
44    }
45
46    /// File-level scope only (path/override based); node overrides ignored.
47    pub fn file_scope(&self, file: &str) -> CorpusScope {
48        self.scope_of_id(file, file)
49    }
50}
51
52impl Store {
53    pub fn scope_index(&self) -> Result<ScopeIndex, StoreError> {
54        let files = self.file_scopes()?;
55        let txn = self.db.begin_read()?;
56        let table = txn.open_table(NODE_SCOPE)?;
57        let mut nodes = HashMap::new();
58        for entry in table.iter()? {
59            let (id, scope) = entry?;
60            if let Some(scope) = CorpusScope::from_str_opt(scope.value()) {
61                nodes.insert(id.value().to_string(), scope);
62            }
63        }
64        Ok(ScopeIndex { files, nodes })
65    }
66
67    pub fn node_scope(&self, node: &Node) -> Result<CorpusScope, StoreError> {
68        let txn = self.db.begin_read()?;
69        let nodes = txn.open_table(NODE_SCOPE)?;
70        let files = txn.open_table(FILE_SCOPE)?;
71        let lookup = |key: &str| {
72            nodes
73                .get(key)
74                .ok()
75                .flatten()
76                .and_then(|g| CorpusScope::from_str_opt(g.value()))
77        };
78        let file_scope = files
79            .get(node.file.as_str())?
80            .and_then(|g| CorpusScope::from_str_opt(g.value()));
81        Ok(resolve(lookup, file_scope, node.id.as_str(), &node.file))
82    }
83}