cherenkov/model/index/
reference.rs1use super::{
4 Catalog, ModelEntry, Source, disk,
5 selector::{self, Selector},
6};
7use anyhow::{Context, Result, ensure};
8use std::path::Path;
9
10pub(super) fn lookup<'a>(catalog: &'a Catalog, reference: &str) -> Result<&'a ModelEntry> {
12 let selector = selector::parse(Path::new(reference))?;
13
14 find(catalog, &selector, reference)?
15 .with_context(|| format!("model {reference:?} is not registered"))
16}
17
18pub(super) fn find<'a>(
21 catalog: &'a Catalog,
22 selector: &Selector,
23 reference: &str,
24) -> Result<Option<&'a ModelEntry>> {
25 if let Selector::Registered(key) = selector {
26 return Ok(catalog.models.get(key).or_else(|| {
27 catalog
28 .models
29 .values()
30 .find(|model| model.name.as_deref() == Some(key.as_str()))
31 }));
32 }
33
34 if let Selector::Disk { store, .. } = selector {
35 disk::registered(catalog, store)?;
36 }
37
38 let mut matches = catalog
39 .models
40 .values()
41 .filter(|model| matches_selector(catalog, &model.source, selector));
42 let first = matches.next();
43
44 ensure!(
45 matches.next().is_none(),
46 "model reference {reference:?} is ambiguous; append @revision or use an alias or model ID"
47 );
48
49 Ok(first)
50}
51
52pub(super) fn preferred(catalog: &Catalog, model: &ModelEntry) -> String {
54 if let Some(name) = &model.name {
55 return name.clone();
56 }
57
58 let (reference, revision) = display_source(catalog, &model.source);
59
60 if selects(catalog, &reference, &model.id) {
61 return reference;
62 }
63
64 if let Some(revision) = revision
65 && is_revision(&revision)
66 {
67 for length in 8..=revision.len() {
68 let qualified = format!("{reference}@{}", &revision[..length]);
69
70 if selects(catalog, &qualified, &model.id) {
71 return qualified;
72 }
73 }
74 }
75
76 model.id.clone()
77}
78
79fn display_source(catalog: &Catalog, source: &Source) -> (String, Option<String>) {
81 match source {
82 Source::HuggingFace { repo, revision, .. } => {
83 (format!("hf://{repo}"), Some(revision.clone()))
84 }
85 Source::Local { path, fingerprint } => {
86 for store in catalog.stores.values().filter(|store| store.enabled) {
87 if let Some(Selector::Disk {
88 store,
89 repo,
90 revision,
91 }) = store.selector(path)
92 {
93 return (
94 format!("disk://{store}/{repo}"),
95 Some(revision.unwrap_or_else(|| fingerprint.clone())),
96 );
97 }
98 }
99
100 (path.to_string_lossy().into_owned(), None)
101 }
102 }
103}
104
105fn selects(catalog: &Catalog, reference: &str, id: &str) -> bool {
107 lookup(catalog, reference).is_ok_and(|model| model.id == id)
108}
109
110fn matches_selector(catalog: &Catalog, source: &Source, selector: &Selector) -> bool {
112 match (source, selector) {
113 (
114 Source::HuggingFace { repo, revision, .. },
115 Selector::Hub {
116 repo: wanted,
117 revision: rev,
118 },
119 ) => {
120 repo == wanted
121 && rev
122 .as_deref()
123 .is_none_or(|r| is_revision(r) && revision.starts_with(r))
124 }
125 (Source::Local { path, .. }, Selector::Path(wanted)) => {
126 let absolute = crate::config::absolute(wanted).ok();
127
128 absolute.is_some_and(|p| path == &p.canonicalize().unwrap_or(p))
129 }
130 (
131 Source::Local { path, fingerprint },
132 Selector::Disk {
133 store,
134 repo,
135 revision,
136 },
137 ) => {
138 let Some(store) = catalog.stores.get(store) else {
139 return false;
140 };
141 let Some(Selector::Disk {
142 repo: found,
143 revision: commit,
144 ..
145 }) = store.selector(path)
146 else {
147 return false;
148 };
149
150 found == *repo
151 && revision.as_deref().is_none_or(|r| {
152 is_revision(r) && commit.as_deref().unwrap_or(fingerprint).starts_with(r)
153 })
154 }
155 (
156 Source::Local { path, fingerprint },
157 Selector::LocalRevision {
158 path: wanted,
159 revision,
160 },
161 ) => path == wanted && fingerprint.starts_with(revision),
162 _ => false,
163 }
164}
165
166pub(super) fn is_revision(value: &str) -> bool {
168 value.len() >= 8 && value.bytes().all(|byte| byte.is_ascii_hexdigit())
169}