theme_registry.rs

  1use anyhow::{anyhow, Context, Result};
  2use gpui::{fonts, AssetSource, FontCache};
  3use parking_lot::Mutex;
  4use serde_json::{Map, Value};
  5use std::{collections::HashMap, fmt, mem, sync::Arc};
  6
  7use super::Theme;
  8
  9pub struct ThemeRegistry {
 10    assets: Box<dyn AssetSource>,
 11    themes: Mutex<HashMap<String, Arc<Theme>>>,
 12    theme_data: Mutex<HashMap<String, Arc<Value>>>,
 13    font_cache: Arc<FontCache>,
 14}
 15
 16#[derive(Default)]
 17struct KeyPathReferenceSet {
 18    references: Vec<KeyPathReference>,
 19    reference_ids_by_source: Vec<usize>,
 20    reference_ids_by_target: Vec<usize>,
 21    dependencies: Vec<(usize, usize)>,
 22    dependency_counts: Vec<usize>,
 23}
 24
 25#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
 26struct KeyPathReference {
 27    target: KeyPath,
 28    source: KeyPath,
 29}
 30
 31#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
 32struct KeyPath(Vec<Key>);
 33
 34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
 35enum Key {
 36    Array(usize),
 37    Object(String),
 38}
 39
 40impl ThemeRegistry {
 41    pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
 42        Arc::new(Self {
 43            assets: Box::new(source),
 44            themes: Default::default(),
 45            theme_data: Default::default(),
 46            font_cache,
 47        })
 48    }
 49
 50    pub fn list(&self) -> impl Iterator<Item = String> {
 51        self.assets.list("themes/").into_iter().filter_map(|path| {
 52            let filename = path.strip_prefix("themes/")?;
 53            let theme_name = filename.strip_suffix(".toml")?;
 54            if theme_name.starts_with('_') {
 55                None
 56            } else {
 57                Some(theme_name.to_string())
 58            }
 59        })
 60    }
 61
 62    pub fn clear(&self) {
 63        self.theme_data.lock().clear();
 64        self.themes.lock().clear();
 65    }
 66
 67    pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
 68        if let Some(theme) = self.themes.lock().get(name) {
 69            return Ok(theme.clone());
 70        }
 71
 72        let theme_data = self.load(name, true)?;
 73        let mut theme: Theme = fonts::with_font_cache(self.font_cache.clone(), || {
 74            serde_path_to_error::deserialize(theme_data.as_ref())
 75        })?;
 76
 77        theme.name = name.into();
 78        let theme = Arc::new(theme);
 79        self.themes.lock().insert(name.to_string(), theme.clone());
 80        Ok(theme)
 81    }
 82
 83    fn load(&self, name: &str, evaluate_references: bool) -> Result<Arc<Value>> {
 84        if let Some(data) = self.theme_data.lock().get(name) {
 85            return Ok(data.clone());
 86        }
 87
 88        let asset_path = format!("themes/{}.toml", name);
 89        let source_code = self
 90            .assets
 91            .load(&asset_path)
 92            .with_context(|| format!("failed to load theme file {}", asset_path))?;
 93
 94        let mut theme_data: Map<String, Value> = toml::from_slice(source_code.as_ref())
 95            .with_context(|| format!("failed to parse {}.toml", name))?;
 96
 97        // If this theme extends another base theme, deeply merge it into the base theme's data
 98        if let Some(base_name) = theme_data
 99            .get("extends")
100            .and_then(|name| name.as_str())
101            .map(str::to_string)
102        {
103            let base_theme_data = self
104                .load(&base_name, false)
105                .with_context(|| format!("failed to load base theme {}", base_name))?
106                .as_ref()
107                .clone();
108            if let Value::Object(mut base_theme_object) = base_theme_data {
109                deep_merge_json(&mut base_theme_object, theme_data);
110                theme_data = base_theme_object;
111            }
112        }
113
114        // Find all of the key path references in the object, and then sort them according
115        // to their dependencies.
116        if evaluate_references {
117            let mut key_path = KeyPath::default();
118            let mut references = KeyPathReferenceSet::default();
119            for (key, value) in theme_data.iter() {
120                key_path.0.push(Key::Object(key.clone()));
121                find_references(value, &mut key_path, &mut references);
122                key_path.0.pop();
123            }
124            let sorted_references = references
125                .top_sort()
126                .map_err(|key_paths| anyhow!("cycle for key paths: {:?}", key_paths))?;
127
128            // Now update objects to include the fields of objects they extend
129            for KeyPathReference { source, target } in sorted_references {
130                if let Some(source) = value_at(&mut theme_data, &source).cloned() {
131                    let target = value_at(&mut theme_data, &target).unwrap();
132                    if let Value::Object(target_object) = target.take() {
133                        if let Value::Object(mut source_object) = source {
134                            deep_merge_json(&mut source_object, target_object);
135                            *target = Value::Object(source_object);
136                        } else {
137                            Err(anyhow!("extended key path {} is not an object", source))?;
138                        }
139                    } else {
140                        *target = source;
141                    }
142                } else {
143                    Err(anyhow!("invalid key path '{}'", source))?;
144                }
145            }
146        }
147
148        let result = Arc::new(Value::Object(theme_data));
149        self.theme_data
150            .lock()
151            .insert(name.to_string(), result.clone());
152
153        Ok(result)
154    }
155}
156
157impl KeyPathReferenceSet {
158    fn insert(&mut self, reference: KeyPathReference) {
159        let id = self.references.len();
160        let source_ix = self
161            .reference_ids_by_source
162            .binary_search_by_key(&&reference.source, |id| &self.references[*id].source)
163            .unwrap_or_else(|i| i);
164        let target_ix = self
165            .reference_ids_by_target
166            .binary_search_by_key(&&reference.target, |id| &self.references[*id].target)
167            .unwrap_or_else(|i| i);
168
169        self.populate_dependencies(id, &reference);
170        self.reference_ids_by_source.insert(source_ix, id);
171        self.reference_ids_by_target.insert(target_ix, id);
172        self.references.push(reference);
173    }
174
175    fn top_sort(mut self) -> Result<Vec<KeyPathReference>, Vec<KeyPath>> {
176        let mut results = Vec::with_capacity(self.references.len());
177        let mut root_ids = Vec::with_capacity(self.references.len());
178
179        // Find the initial set of references that have no dependencies.
180        for (id, dep_count) in self.dependency_counts.iter().enumerate() {
181            if *dep_count == 0 {
182                root_ids.push(id);
183            }
184        }
185
186        while results.len() < root_ids.len() {
187            // Just to guarantee a stable result when the inputs are randomized,
188            // sort references lexicographically in absence of any dependency relationship.
189            root_ids[results.len()..].sort_by_key(|id| &self.references[*id]);
190
191            let root_id = root_ids[results.len()];
192            let root = mem::take(&mut self.references[root_id]);
193            results.push(root);
194
195            // Remove this reference as a dependency from any of its dependent references.
196            if let Ok(dep_ix) = self
197                .dependencies
198                .binary_search_by_key(&root_id, |edge| edge.0)
199            {
200                let mut first_dep_ix = dep_ix;
201                let mut last_dep_ix = dep_ix + 1;
202                while first_dep_ix > 0 && self.dependencies[first_dep_ix - 1].0 == root_id {
203                    first_dep_ix -= 1;
204                }
205                while last_dep_ix < self.dependencies.len()
206                    && self.dependencies[last_dep_ix].0 == root_id
207                {
208                    last_dep_ix += 1;
209                }
210
211                // If any reference no longer has any dependencies, then then mark it as a root.
212                // Preserve the references' original order where possible.
213                for (_, successor_id) in self.dependencies.drain(first_dep_ix..last_dep_ix) {
214                    self.dependency_counts[successor_id] -= 1;
215                    if self.dependency_counts[successor_id] == 0 {
216                        root_ids.push(successor_id);
217                    }
218                }
219            }
220        }
221
222        // If any references never became roots, then there are reference cycles
223        // in the set. Return an error containing all of the key paths that are
224        // directly involved in cycles.
225        if results.len() < self.references.len() {
226            let mut cycle_ref_ids = (0..self.references.len())
227                .filter(|id| !root_ids.contains(id))
228                .collect::<Vec<_>>();
229
230            // Iteratively remove any references that have no dependencies,
231            // so that the error will only indicate which key paths are directly
232            // involved in the cycles.
233            let mut done = false;
234            while !done {
235                done = true;
236                cycle_ref_ids.retain(|id| {
237                    if self.dependencies.iter().any(|dep| dep.0 == *id) {
238                        true
239                    } else {
240                        done = false;
241                        self.dependencies.retain(|dep| dep.1 != *id);
242                        false
243                    }
244                });
245            }
246
247            let mut cycle_key_paths = Vec::new();
248            for id in cycle_ref_ids {
249                let reference = &self.references[id];
250                cycle_key_paths.push(reference.target.clone());
251                cycle_key_paths.push(reference.source.clone());
252            }
253            cycle_key_paths.sort_unstable();
254            return Err(cycle_key_paths);
255        }
256
257        Ok(results)
258    }
259
260    fn populate_dependencies(&mut self, new_id: usize, new_reference: &KeyPathReference) {
261        self.dependency_counts.push(0);
262
263        // If an existing reference's source path starts with the new reference's
264        // target path, then insert this new reference before that existing reference.
265        for id in Self::reference_ids_for_key_path(
266            &new_reference.target.0,
267            &self.references,
268            &self.reference_ids_by_source,
269            KeyPathReference::source,
270            KeyPath::starts_with,
271        ) {
272            Self::add_dependency(
273                (new_id, id),
274                &mut self.dependencies,
275                &mut self.dependency_counts,
276            );
277        }
278
279        // If an existing reference's target path starts with the new reference's
280        // source path, then insert this new reference after that existing reference.
281        for id in Self::reference_ids_for_key_path(
282            &new_reference.source.0,
283            &self.references,
284            &self.reference_ids_by_target,
285            KeyPathReference::target,
286            KeyPath::starts_with,
287        ) {
288            Self::add_dependency(
289                (id, new_id),
290                &mut self.dependencies,
291                &mut self.dependency_counts,
292            );
293        }
294
295        // If an existing reference's source path is a prefix of the new reference's
296        // target path, then insert this new reference before that existing reference.
297        for prefix in new_reference.target.prefixes() {
298            for id in Self::reference_ids_for_key_path(
299                prefix,
300                &self.references,
301                &self.reference_ids_by_source,
302                KeyPathReference::source,
303                PartialEq::eq,
304            ) {
305                Self::add_dependency(
306                    (new_id, id),
307                    &mut self.dependencies,
308                    &mut self.dependency_counts,
309                );
310            }
311        }
312
313        // If an existing reference's target path is a prefix of the new reference's
314        // source path, then insert this new reference after that existing reference.
315        for prefix in new_reference.source.prefixes() {
316            for id in Self::reference_ids_for_key_path(
317                prefix,
318                &self.references,
319                &self.reference_ids_by_target,
320                KeyPathReference::target,
321                PartialEq::eq,
322            ) {
323                Self::add_dependency(
324                    (id, new_id),
325                    &mut self.dependencies,
326                    &mut self.dependency_counts,
327                );
328            }
329        }
330
331        // If an existing reference's target path is a prefix of the new reference's target path,
332        // then insert this new reference before that existing reference.
333        for prefix in new_reference.target.prefixes() {
334            for id in Self::reference_ids_for_key_path(
335                prefix,
336                &self.references,
337                &self.reference_ids_by_target,
338                KeyPathReference::target,
339                PartialEq::eq,
340            ) {
341                Self::add_dependency(
342                    (new_id, id),
343                    &mut self.dependencies,
344                    &mut self.dependency_counts,
345                );
346            }
347        }
348    }
349
350    // Find all existing references that satisfy a given predicate with respect
351    // to a given key path. Use a sorted array of reference ids in order to avoid
352    // performing unnecessary comparisons.
353    fn reference_ids_for_key_path<'a>(
354        key_path: &[Key],
355        references: &[KeyPathReference],
356        sorted_reference_ids: &'a [usize],
357        reference_attribute: impl Fn(&KeyPathReference) -> &KeyPath,
358        predicate: impl Fn(&KeyPath, &[Key]) -> bool,
359    ) -> impl Iterator<Item = usize> + 'a {
360        let ix = sorted_reference_ids
361            .binary_search_by_key(&key_path, |id| &reference_attribute(&references[*id]).0)
362            .unwrap_or_else(|i| i);
363
364        let mut start_ix = ix;
365        while start_ix > 0 {
366            let reference_id = sorted_reference_ids[start_ix - 1];
367            let reference = &references[reference_id];
368            if !predicate(&reference_attribute(reference), key_path) {
369                break;
370            }
371            start_ix -= 1;
372        }
373
374        let mut end_ix = ix;
375        while end_ix < sorted_reference_ids.len() {
376            let reference_id = sorted_reference_ids[end_ix];
377            let reference = &references[reference_id];
378            if !predicate(&reference_attribute(reference), key_path) {
379                break;
380            }
381            end_ix += 1;
382        }
383
384        sorted_reference_ids[start_ix..end_ix].iter().copied()
385    }
386
387    fn add_dependency(
388        (predecessor, successor): (usize, usize),
389        dependencies: &mut Vec<(usize, usize)>,
390        dependency_counts: &mut Vec<usize>,
391    ) {
392        let dependency = (predecessor, successor);
393        if let Err(i) = dependencies.binary_search(&dependency) {
394            dependencies.insert(i, dependency);
395        }
396        dependency_counts[successor] += 1;
397    }
398}
399
400impl KeyPathReference {
401    fn source(&self) -> &KeyPath {
402        &self.source
403    }
404
405    fn target(&self) -> &KeyPath {
406        &self.target
407    }
408}
409
410impl KeyPath {
411    fn new(string: &str) -> Self {
412        Self(
413            string
414                .split(".")
415                .map(|key| Key::Object(key.to_string()))
416                .collect(),
417        )
418    }
419
420    fn starts_with(&self, other: &[Key]) -> bool {
421        self.0.starts_with(&other)
422    }
423
424    fn prefixes(&self) -> impl Iterator<Item = &[Key]> {
425        (1..self.0.len()).map(move |end_ix| &self.0[0..end_ix])
426    }
427}
428
429impl PartialEq<[Key]> for KeyPath {
430    fn eq(&self, other: &[Key]) -> bool {
431        self.0.eq(other)
432    }
433}
434
435impl fmt::Debug for KeyPathReference {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        write!(
438            f,
439            "KeyPathReference {{ {} <- {} }}",
440            self.target, self.source
441        )
442    }
443}
444
445impl fmt::Display for KeyPath {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        for (i, key) in self.0.iter().enumerate() {
448            match key {
449                Key::Array(index) => write!(f, "[{}]", index)?,
450                Key::Object(key) => {
451                    if i > 0 {
452                        ".".fmt(f)?;
453                    }
454                    key.fmt(f)?;
455                }
456            }
457        }
458        Ok(())
459    }
460}
461
462fn deep_merge_json(base: &mut Map<String, Value>, extension: Map<String, Value>) {
463    for (key, extension_value) in extension {
464        if let Value::Object(extension_object) = extension_value {
465            if let Some(base_object) = base.get_mut(&key).and_then(|value| value.as_object_mut()) {
466                deep_merge_json(base_object, extension_object);
467            } else {
468                base.insert(key, Value::Object(extension_object));
469            }
470        } else {
471            base.insert(key, extension_value);
472        }
473    }
474}
475
476fn find_references(value: &Value, key_path: &mut KeyPath, references: &mut KeyPathReferenceSet) {
477    match value {
478        Value::Array(vec) => {
479            for (ix, value) in vec.iter().enumerate() {
480                key_path.0.push(Key::Array(ix));
481                find_references(value, key_path, references);
482                key_path.0.pop();
483            }
484        }
485        Value::Object(map) => {
486            for (key, value) in map.iter() {
487                if key == "extends" {
488                    if let Some(source_path) = value.as_str().and_then(|s| s.strip_prefix("$")) {
489                        references.insert(KeyPathReference {
490                            source: KeyPath::new(source_path),
491                            target: key_path.clone(),
492                        });
493                    }
494                } else {
495                    key_path.0.push(Key::Object(key.to_string()));
496                    find_references(value, key_path, references);
497                    key_path.0.pop();
498                }
499            }
500        }
501        Value::String(string) => {
502            if let Some(source_path) = string.strip_prefix("$") {
503                references.insert(KeyPathReference {
504                    source: KeyPath::new(source_path),
505                    target: key_path.clone(),
506                });
507            }
508        }
509        _ => {}
510    }
511}
512
513fn value_at<'a>(object: &'a mut Map<String, Value>, key_path: &KeyPath) -> Option<&'a mut Value> {
514    let mut key_path = key_path.0.iter();
515    if let Some(Key::Object(first_key)) = key_path.next() {
516        let mut cur_value = object.get_mut(first_key);
517        for key in key_path {
518            if let Some(value) = cur_value {
519                match key {
520                    Key::Array(ix) => cur_value = value.get_mut(ix),
521                    Key::Object(key) => cur_value = value.get_mut(key),
522                }
523            } else {
524                return None;
525            }
526        }
527        cur_value
528    } else {
529        None
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use crate::{test::test_app_state, theme::DEFAULT_THEME_NAME};
537    use gpui::MutableAppContext;
538    use rand::{prelude::StdRng, Rng};
539
540    #[gpui::test]
541    fn test_bundled_themes(cx: &mut MutableAppContext) {
542        let app_state = test_app_state(cx);
543        let mut has_default_theme = false;
544        for theme_name in app_state.themes.list() {
545            let theme = app_state.themes.get(&theme_name).unwrap();
546            if theme.name == DEFAULT_THEME_NAME {
547                has_default_theme = true;
548            }
549            assert_eq!(theme.name, theme_name);
550        }
551        assert!(has_default_theme);
552    }
553
554    #[gpui::test]
555    fn test_theme_extension(cx: &mut MutableAppContext) {
556        let assets = TestAssets(&[
557            (
558                "themes/_base.toml",
559                r##"
560                [ui.active_tab]
561                extends = "$ui.tab"
562                border.color = "#666666"
563                text = "$text_colors.bright"
564
565                [ui.tab]
566                extends = "$ui.element"
567                text = "$text_colors.dull"
568
569                [ui.element]
570                background = "#111111"
571                border = {width = 2.0, color = "#00000000"}
572
573                [editor]
574                background = "#222222"
575                default_text = "$text_colors.regular"
576                "##,
577            ),
578            (
579                "themes/light.toml",
580                r##"
581                extends = "_base"
582
583                [text_colors]
584                bright = "#ffffff"
585                regular = "#eeeeee"
586                dull = "#dddddd"
587
588                [editor]
589                background = "#232323"
590                "##,
591            ),
592        ]);
593
594        let registry = ThemeRegistry::new(assets, cx.font_cache().clone());
595        let theme_data = registry.load("light", true).unwrap();
596        assert_eq!(
597            theme_data.as_ref(),
598            &serde_json::json!({
599              "ui": {
600                "active_tab": {
601                  "background": "#111111",
602                  "border": {
603                    "width": 2.0,
604                    "color": "#666666"
605                  },
606                  "extends": "$ui.tab",
607                  "text": "#ffffff"
608                },
609                "tab": {
610                  "background": "#111111",
611                  "border": {
612                    "width": 2.0,
613                    "color": "#00000000"
614                  },
615                  "extends": "$ui.element",
616                  "text": "#dddddd"
617                },
618                "element": {
619                  "background": "#111111",
620                  "border": {
621                    "width": 2.0,
622                    "color": "#00000000"
623                  }
624                }
625              },
626              "editor": {
627                "background": "#232323",
628                "default_text": "#eeeeee"
629              },
630              "extends": "_base",
631              "text_colors": {
632                "bright": "#ffffff",
633                "regular": "#eeeeee",
634                "dull": "#dddddd"
635              }
636            })
637        );
638    }
639
640    #[gpui::test]
641    fn test_nested_extension(cx: &mut MutableAppContext) {
642        let assets = TestAssets(&[(
643            "themes/theme.toml",
644            r##"
645                [a]
646                text = { extends = "$text.0" }
647
648                [b]
649                extends = "$a"
650                text = { extends = "$text.1" }
651
652                [text]
653                0 = { color = "red" }
654                1 = { color = "blue" }
655            "##,
656        )]);
657
658        let registry = ThemeRegistry::new(assets, cx.font_cache().clone());
659        let theme_data = registry.load("theme", true).unwrap();
660        assert_eq!(
661            theme_data
662                .get("b")
663                .unwrap()
664                .get("text")
665                .unwrap()
666                .get("color")
667                .unwrap(),
668            "blue"
669        );
670    }
671
672    #[test]
673    fn test_key_path_reference_set_simple() {
674        let input_references = build_refs(&[
675            ("r", "a"),
676            ("a.b.c", "d"),
677            ("d.e", "f"),
678            ("t.u", "v"),
679            ("v.w", "x"),
680            ("v.y", "x"),
681            ("d.h", "i"),
682            ("v.z", "x"),
683            ("f.g", "d.h"),
684        ]);
685        let expected_references = build_refs(&[
686            ("d.h", "i"),
687            ("f.g", "d.h"),
688            ("d.e", "f"),
689            ("a.b.c", "d"),
690            ("r", "a"),
691            ("v.w", "x"),
692            ("v.y", "x"),
693            ("v.z", "x"),
694            ("t.u", "v"),
695        ])
696        .collect::<Vec<_>>();
697
698        let mut reference_set = KeyPathReferenceSet::default();
699        for reference in input_references {
700            reference_set.insert(reference);
701        }
702        assert_eq!(reference_set.top_sort().unwrap(), expected_references);
703    }
704
705    #[test]
706    fn test_key_path_reference_set_with_cycles() {
707        let input_references = build_refs(&[
708            ("x", "a.b"),
709            ("y", "x.c"),
710            ("a.b.c", "d.e"),
711            ("d.e.f", "g.h"),
712            ("g.h.i", "a"),
713        ]);
714
715        let mut reference_set = KeyPathReferenceSet::default();
716        for reference in input_references {
717            reference_set.insert(reference);
718        }
719
720        assert_eq!(
721            reference_set.top_sort().unwrap_err(),
722            &[
723                KeyPath::new("a"),
724                KeyPath::new("a.b.c"),
725                KeyPath::new("d.e"),
726                KeyPath::new("d.e.f"),
727                KeyPath::new("g.h"),
728                KeyPath::new("g.h.i"),
729            ]
730        );
731    }
732
733    #[gpui::test(iterations = 20)]
734    async fn test_key_path_reference_set_random(mut rng: StdRng) {
735        let examples: &[&[_]] = &[
736            &[
737                ("n.d.h", "i"),
738                ("f.g", "n.d.h"),
739                ("n.d.e", "f"),
740                ("a.b.c", "n.d"),
741                ("r", "a"),
742                ("q.q.q", "r.s"),
743                ("r.t", "q"),
744                ("x.x", "r.r"),
745                ("v.w", "x"),
746                ("v.y", "x"),
747                ("v.z", "x"),
748                ("t.u", "v"),
749            ],
750            &[
751                ("w.x.y.z", "t.u.z"),
752                ("x", "w.x"),
753                ("a.b.c1", "x.b1.c"),
754                ("a.b.c2", "x.b2.c"),
755            ],
756            &[
757                ("x.y", "m.n.n.o.q"),
758                ("x.y.z", "m.n.n.o.p"),
759                ("u.v.w", "x.y.z"),
760                ("a.b.c.d", "u.v"),
761                ("a.b.c.d.e", "u.v"),
762                ("a.b.c.d.f", "u.v"),
763                ("a.b.c.d.g", "u.v"),
764            ],
765        ];
766
767        for example in examples {
768            let expected_references = build_refs(example).collect::<Vec<_>>();
769            let mut input_references = expected_references.clone();
770            input_references.sort_by_key(|_| rng.gen_range(0..1000));
771            let mut reference_set = KeyPathReferenceSet::default();
772            for reference in input_references {
773                reference_set.insert(reference);
774            }
775            assert_eq!(reference_set.top_sort().unwrap(), expected_references);
776        }
777    }
778
779    fn build_refs<'a>(rows: &'a [(&str, &str)]) -> impl Iterator<Item = KeyPathReference> + 'a {
780        rows.iter().map(|(target, source)| KeyPathReference {
781            target: KeyPath::new(target),
782            source: KeyPath::new(source),
783        })
784    }
785
786    struct TestAssets(&'static [(&'static str, &'static str)]);
787
788    impl AssetSource for TestAssets {
789        fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
790            if let Some(row) = self.0.iter().find(|e| e.0 == path) {
791                Ok(row.1.as_bytes().into())
792            } else {
793                Err(anyhow!("no such path {}", path))
794            }
795        }
796
797        fn list(&self, prefix: &str) -> Vec<std::borrow::Cow<'static, str>> {
798            self.0
799                .iter()
800                .copied()
801                .filter_map(|(path, _)| {
802                    if path.starts_with(prefix) {
803                        Some(path.into())
804                    } else {
805                        None
806                    }
807                })
808                .collect()
809        }
810    }
811}