mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 20:25:44 +03:00
Switch to new resolution system in ThemeRegistry
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
5ee0e85f02
commit
95ef70e4f4
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5917,6 +5917,7 @@ dependencies = [
|
||||
"http-auth-basic",
|
||||
"ignore",
|
||||
"image 0.23.14",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -32,6 +32,7 @@ gpui = { path = "../gpui" }
|
||||
http-auth-basic = "0.1.3"
|
||||
ignore = "0.4"
|
||||
image = "0.23"
|
||||
indexmap = "1.6.2"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
|
@ -67,6 +67,12 @@ sender = { extends = "$text.0", weight = "bold", margin.right = 8 }
|
||||
timestamp = "$text.2"
|
||||
padding.bottom = 6
|
||||
|
||||
[chat_panel.pending_message]
|
||||
extends = "$chat_panel.message"
|
||||
body = { color = "$text.3.color" }
|
||||
sender = { color = "$text.3.color" }
|
||||
timestamp = { color = "$text.3.color" }
|
||||
|
||||
[chat_panel.channel_select.item]
|
||||
padding = 4
|
||||
name = "$text.1"
|
||||
|
@ -1,5 +1,5 @@
|
||||
mod highlight_map;
|
||||
mod resolve_tree;
|
||||
mod resolution;
|
||||
mod theme_registry;
|
||||
|
||||
use anyhow::Result;
|
||||
|
441
zed/src/theme/resolution.rs
Normal file
441
zed/src/theme/resolution.rs
Normal file
@ -0,0 +1,441 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use indexmap::IndexMap;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
mem,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
pub fn resolve_references(value: Value) -> Result<Value> {
|
||||
let tree = Tree::from_json(value)?;
|
||||
tree.resolve()?;
|
||||
tree.to_json()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Node {
|
||||
Reference {
|
||||
path: String,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
Object {
|
||||
base: Option<String>,
|
||||
children: IndexMap<String, Tree>,
|
||||
resolved: bool,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
Array {
|
||||
children: Vec<Tree>,
|
||||
resolved: bool,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
String(String),
|
||||
Number(serde_json::Number),
|
||||
Bool(bool),
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Tree(Rc<RefCell<Node>>);
|
||||
|
||||
impl Tree {
|
||||
pub fn new(node: Node) -> Self {
|
||||
Self(Rc::new(RefCell::new(node)))
|
||||
}
|
||||
|
||||
fn from_json(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::String(value) => {
|
||||
if let Some(path) = value.strip_prefix("$") {
|
||||
Ok(Self::new(Node::Reference {
|
||||
path: path.to_string(),
|
||||
parent: None,
|
||||
}))
|
||||
} else {
|
||||
Ok(Self::new(Node::String(value)))
|
||||
}
|
||||
}
|
||||
Value::Number(value) => Ok(Self::new(Node::Number(value))),
|
||||
Value::Bool(value) => Ok(Self::new(Node::Bool(value))),
|
||||
Value::Null => Ok(Self::new(Node::Null)),
|
||||
Value::Object(object) => {
|
||||
let tree = Self::new(Node::Object {
|
||||
base: Default::default(),
|
||||
children: Default::default(),
|
||||
resolved: false,
|
||||
parent: None,
|
||||
});
|
||||
let mut children = IndexMap::new();
|
||||
let mut resolved = true;
|
||||
let mut base = None;
|
||||
for (key, value) in object.into_iter() {
|
||||
let value = if key == "extends" {
|
||||
if value.is_string() {
|
||||
if let Value::String(value) = value {
|
||||
base = value.strip_prefix("$").map(str::to_string);
|
||||
resolved = false;
|
||||
Self::new(Node::String(value))
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
Tree::from_json(value)?
|
||||
}
|
||||
} else {
|
||||
Tree::from_json(value)?
|
||||
};
|
||||
value
|
||||
.0
|
||||
.borrow_mut()
|
||||
.set_parent(Some(Rc::downgrade(&tree.0)));
|
||||
resolved &= value.is_resolved();
|
||||
children.insert(key.clone(), value);
|
||||
}
|
||||
|
||||
*tree.0.borrow_mut() = Node::Object {
|
||||
base,
|
||||
children,
|
||||
resolved,
|
||||
parent: None,
|
||||
};
|
||||
Ok(tree)
|
||||
}
|
||||
Value::Array(elements) => {
|
||||
let tree = Self::new(Node::Array {
|
||||
children: Default::default(),
|
||||
resolved: false,
|
||||
parent: None,
|
||||
});
|
||||
|
||||
let mut children = Vec::new();
|
||||
let mut resolved = true;
|
||||
for element in elements {
|
||||
let child = Tree::from_json(element)?;
|
||||
child
|
||||
.0
|
||||
.borrow_mut()
|
||||
.set_parent(Some(Rc::downgrade(&tree.0)));
|
||||
resolved &= child.is_resolved();
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
*tree.0.borrow_mut() = Node::Array {
|
||||
children,
|
||||
resolved,
|
||||
parent: None,
|
||||
};
|
||||
Ok(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<Value> {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { .. } => Err(anyhow!("unresolved tree")),
|
||||
Node::String(value) => Ok(Value::String(value.clone())),
|
||||
Node::Number(value) => Ok(Value::Number(value.clone())),
|
||||
Node::Bool(value) => Ok(Value::Bool(*value)),
|
||||
Node::Null => Ok(Value::Null),
|
||||
Node::Object { children, .. } => {
|
||||
let mut json_children = serde_json::Map::new();
|
||||
for (key, value) in children {
|
||||
json_children.insert(key.clone(), value.to_json()?);
|
||||
}
|
||||
Ok(Value::Object(json_children))
|
||||
}
|
||||
Node::Array { children, .. } => {
|
||||
let mut json_children = Vec::new();
|
||||
for child in children {
|
||||
json_children.push(child.to_json()?);
|
||||
}
|
||||
Ok(Value::Array(json_children))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<Tree> {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { parent, .. }
|
||||
| Node::Object { parent, .. }
|
||||
| Node::Array { parent, .. } => parent.as_ref().and_then(|p| p.upgrade()).map(Tree),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, path: &str) -> Result<Option<Tree>> {
|
||||
let mut tree = self.clone();
|
||||
for component in path.split('.') {
|
||||
let node = tree.0.borrow();
|
||||
match &*node {
|
||||
Node::Object { children, .. } => {
|
||||
if let Some(subtree) = children.get(component).cloned() {
|
||||
drop(node);
|
||||
tree = subtree;
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"key \"{}\" does not exist in path \"{}\"",
|
||||
component,
|
||||
path
|
||||
));
|
||||
}
|
||||
}
|
||||
Node::Reference { .. } => return Ok(None),
|
||||
Node::Array { .. }
|
||||
| Node::String(_)
|
||||
| Node::Number(_)
|
||||
| Node::Bool(_)
|
||||
| Node::Null => {
|
||||
return Err(anyhow!(
|
||||
"key \"{}\" in path \"{}\" is not an object",
|
||||
component,
|
||||
path
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(tree))
|
||||
}
|
||||
|
||||
fn is_resolved(&self) -> bool {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { .. } => false,
|
||||
Node::Object { resolved, .. } | Node::Array { resolved, .. } => *resolved,
|
||||
Node::String(_) | Node::Number(_) | Node::Bool(_) | Node::Null => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_resolved(&self) {
|
||||
match &mut *self.0.borrow_mut() {
|
||||
Node::Object {
|
||||
resolved, children, ..
|
||||
} => {
|
||||
*resolved = children.values().all(|c| c.is_resolved());
|
||||
}
|
||||
Node::Array {
|
||||
resolved, children, ..
|
||||
} => {
|
||||
*resolved = children.iter().all(|c| c.is_resolved());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self) -> Result<()> {
|
||||
let mut unresolved = vec![self.clone()];
|
||||
let mut made_progress = true;
|
||||
|
||||
while made_progress && !unresolved.is_empty() {
|
||||
made_progress = false;
|
||||
for mut tree in mem::take(&mut unresolved) {
|
||||
made_progress |= tree.resolve_subtree(self, &mut unresolved)?;
|
||||
if tree.is_resolved() {
|
||||
while let Some(parent) = tree.parent() {
|
||||
parent.update_resolved();
|
||||
tree = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if unresolved.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("tree contains cycles"))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_subtree(&self, root: &Tree, unresolved: &mut Vec<Tree>) -> Result<bool> {
|
||||
let mut made_progress = false;
|
||||
let borrow = self.0.borrow();
|
||||
match &*borrow {
|
||||
Node::Reference { path, parent } => {
|
||||
if let Some(subtree) = root.get(&path)? {
|
||||
if subtree.is_resolved() {
|
||||
let parent = parent.clone();
|
||||
drop(borrow);
|
||||
let mut new_node = subtree.0.borrow().clone();
|
||||
new_node.set_parent(parent);
|
||||
*self.0.borrow_mut() = new_node;
|
||||
Ok(true)
|
||||
} else {
|
||||
unresolved.push(self.clone());
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
unresolved.push(self.clone());
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
Node::Object {
|
||||
base,
|
||||
children,
|
||||
resolved,
|
||||
..
|
||||
} => {
|
||||
if *resolved {
|
||||
Ok(false)
|
||||
} else {
|
||||
let mut children_resolved = true;
|
||||
for child in children.values() {
|
||||
made_progress |= child.resolve_subtree(root, unresolved)?;
|
||||
children_resolved &= child.is_resolved();
|
||||
}
|
||||
|
||||
if children_resolved {
|
||||
let mut has_base = false;
|
||||
let mut resolved_base = None;
|
||||
if let Some(base) = base {
|
||||
has_base = true;
|
||||
if let Some(base) = root.get(base)? {
|
||||
if base.is_resolved() {
|
||||
resolved_base = Some(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(borrow);
|
||||
|
||||
if let Some(base) = resolved_base.as_ref() {
|
||||
self.extend_from(&base);
|
||||
}
|
||||
|
||||
if let Node::Object { resolved, .. } = &mut *self.0.borrow_mut() {
|
||||
if has_base {
|
||||
if resolved_base.is_some() {
|
||||
*resolved = true;
|
||||
} else {
|
||||
unresolved.push(self.clone());
|
||||
}
|
||||
} else {
|
||||
*resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(made_progress)
|
||||
}
|
||||
}
|
||||
Node::Array {
|
||||
children, resolved, ..
|
||||
} => {
|
||||
if *resolved {
|
||||
Ok(false)
|
||||
} else {
|
||||
let mut children_resolved = true;
|
||||
for child in children.iter() {
|
||||
made_progress |= child.resolve_subtree(root, unresolved)?;
|
||||
children_resolved &= child.is_resolved();
|
||||
}
|
||||
|
||||
if children_resolved {
|
||||
drop(borrow);
|
||||
|
||||
if let Node::Array { resolved, .. } = &mut *self.0.borrow_mut() {
|
||||
*resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(made_progress)
|
||||
}
|
||||
}
|
||||
Node::String(_) | Node::Number(_) | Node::Bool(_) | Node::Null => {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_from(&self, base: &Tree) {
|
||||
if Rc::ptr_eq(&self.0, &base.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let (
|
||||
Node::Object { children, .. },
|
||||
Node::Object {
|
||||
children: base_children,
|
||||
..
|
||||
},
|
||||
) = (&mut *self.0.borrow_mut(), &*base.0.borrow())
|
||||
{
|
||||
for (key, base_value) in base_children {
|
||||
if let Some(value) = children.get(key) {
|
||||
value.extend_from(base_value);
|
||||
} else {
|
||||
let base_value = base_value.clone();
|
||||
base_value
|
||||
.0
|
||||
.borrow_mut()
|
||||
.set_parent(Some(Rc::downgrade(&self.0)));
|
||||
children.insert(key.clone(), base_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn set_parent(&mut self, new_parent: Option<Weak<RefCell<Node>>>) {
|
||||
match self {
|
||||
Node::Reference { parent, .. }
|
||||
| Node::Object { parent, .. }
|
||||
| Node::Array { parent, .. } => *parent = new_parent,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_references() {
|
||||
let json = serde_json::json!({
|
||||
"a": {
|
||||
"x": "$b.d"
|
||||
},
|
||||
"b": {
|
||||
"c": "$a",
|
||||
"d": "$e.f"
|
||||
},
|
||||
"e": {
|
||||
"extends": "$a",
|
||||
"f": "1"
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
resolve_references(json).unwrap(),
|
||||
serde_json::json!({
|
||||
"e": {
|
||||
"f": "1",
|
||||
"x": "1"
|
||||
},
|
||||
"a": {
|
||||
"x": "1"
|
||||
},
|
||||
"b": {
|
||||
"c": {
|
||||
"x": "1"
|
||||
},
|
||||
"d": "1"
|
||||
}})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cycles() {
|
||||
let json = serde_json::json!({
|
||||
"a": {
|
||||
"b": "$c.d"
|
||||
},
|
||||
"c": {
|
||||
"d": "$a.b",
|
||||
},
|
||||
});
|
||||
|
||||
assert!(resolve_references(json).is_err());
|
||||
}
|
||||
}
|
@ -1,275 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
pub fn resolve(value: Value) -> Result<Value> {
|
||||
let tree = Tree::from_json(value)?;
|
||||
tree.resolve()?;
|
||||
tree.to_json()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Node {
|
||||
Reference {
|
||||
path: String,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
Object {
|
||||
base: Option<String>,
|
||||
children: HashMap<String, Tree>,
|
||||
resolved: bool,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
String {
|
||||
value: String,
|
||||
parent: Option<Weak<RefCell<Node>>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Tree(Rc<RefCell<Node>>);
|
||||
|
||||
impl Tree {
|
||||
pub fn new(node: Node) -> Self {
|
||||
Self(Rc::new(RefCell::new(node)))
|
||||
}
|
||||
|
||||
fn from_json(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::String(s) => {
|
||||
if let Some(path) = s.strip_prefix("$") {
|
||||
Ok(Self::new(Node::Reference {
|
||||
path: path.to_string(),
|
||||
parent: None,
|
||||
}))
|
||||
} else {
|
||||
Ok(Self::new(Node::String {
|
||||
value: s,
|
||||
parent: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
Value::Object(object) => {
|
||||
let mut tree = Self::new(Node::Object {
|
||||
base: Default::default(),
|
||||
children: Default::default(),
|
||||
resolved: false,
|
||||
parent: None,
|
||||
});
|
||||
let mut children = HashMap::new();
|
||||
let mut resolved = true;
|
||||
let mut base = None;
|
||||
for (key, value) in object.into_iter() {
|
||||
if key == "extends" {
|
||||
if let Value::String(s) = value {
|
||||
base = Some(s);
|
||||
resolved = false;
|
||||
}
|
||||
} else {
|
||||
let value = Tree::from_json(value)?;
|
||||
value
|
||||
.0
|
||||
.borrow_mut()
|
||||
.set_parent(Some(Rc::downgrade(&tree.0)));
|
||||
resolved &= value.is_resolved();
|
||||
children.insert(key.clone(), value);
|
||||
}
|
||||
}
|
||||
|
||||
*tree.0.borrow_mut() = Node::Object {
|
||||
base,
|
||||
children,
|
||||
resolved,
|
||||
parent: None,
|
||||
};
|
||||
Ok(tree)
|
||||
}
|
||||
_ => return Err(anyhow!("unsupported json type")),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<Value> {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { .. } => Err(anyhow!("unresolved tree")),
|
||||
Node::String { value, .. } => Ok(Value::String(value.clone())),
|
||||
Node::Object { children, .. } => {
|
||||
let mut json_children = serde_json::Map::new();
|
||||
for (key, value) in children {
|
||||
json_children.insert(key.clone(), value.to_json()?);
|
||||
}
|
||||
Ok(Value::Object(json_children))
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<Tree> {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { parent, .. }
|
||||
| Node::Object { parent, .. }
|
||||
| Node::String { parent, .. } => parent.as_ref().and_then(|p| p.upgrade()).map(Tree),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, path: &str) -> Result<Option<Tree>> {
|
||||
let mut tree = self.clone();
|
||||
for component in path.split('.') {
|
||||
let node = tree.0.borrow();
|
||||
match &*node {
|
||||
Node::Object { children, .. } => {
|
||||
if let Some(subtree) = children.get(component).cloned() {
|
||||
drop(node);
|
||||
tree = subtree;
|
||||
} else {
|
||||
return Err(anyhow!("key does not exist"));
|
||||
}
|
||||
}
|
||||
Node::Reference { .. } => return Ok(None),
|
||||
Node::String { .. } => return Err(anyhow!("component is not an object")),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(tree))
|
||||
}
|
||||
|
||||
fn is_resolved(&self) -> bool {
|
||||
match &*self.0.borrow() {
|
||||
Node::Reference { .. } => false,
|
||||
Node::Object { resolved, .. } => *resolved,
|
||||
Node::String { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_resolved(&self) {
|
||||
match &mut *self.0.borrow_mut() {
|
||||
Node::Object {
|
||||
resolved, children, ..
|
||||
} => {
|
||||
*resolved = children.values().all(|c| c.is_resolved());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(&self) -> Result<()> {
|
||||
let mut unresolved = vec![self.clone()];
|
||||
let mut made_progress = true;
|
||||
while made_progress && !unresolved.is_empty() {
|
||||
made_progress = false;
|
||||
dbg!("===========");
|
||||
for mut tree in mem::take(&mut unresolved) {
|
||||
made_progress |= tree.resolve_subtree(self, &mut unresolved)?;
|
||||
if tree.is_resolved() {
|
||||
while let Some(parent) = tree.parent() {
|
||||
parent.update_resolved();
|
||||
tree = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if unresolved.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("could not resolve tree"))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_subtree(&self, root: &Tree, unresolved: &mut Vec<Tree>) -> Result<bool> {
|
||||
let mut made_progress = false;
|
||||
let borrow = self.0.borrow();
|
||||
match &*borrow {
|
||||
Node::Reference { path, parent } => {
|
||||
print!("entering reference ${}: ", path);
|
||||
if let Some(subtree) = root.get(&path)? {
|
||||
if subtree.is_resolved() {
|
||||
println!("resolved");
|
||||
let parent = parent.clone();
|
||||
drop(borrow);
|
||||
let mut new_node = subtree.0.borrow().clone();
|
||||
new_node.set_parent(parent);
|
||||
*self.0.borrow_mut() = new_node;
|
||||
Ok(true)
|
||||
} else {
|
||||
println!("unresolved (but existing)");
|
||||
unresolved.push(self.clone());
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
println!("unresolved (referant does not exist)");
|
||||
unresolved.push(self.clone());
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
Node::Object {
|
||||
base,
|
||||
children,
|
||||
resolved,
|
||||
..
|
||||
} => {
|
||||
if *resolved {
|
||||
println!("already resolved");
|
||||
Ok(false)
|
||||
} else {
|
||||
let mut children_resolved = true;
|
||||
for (key, child) in children.iter() {
|
||||
println!("resolving subtree {}", key);
|
||||
made_progress |= child.resolve_subtree(root, unresolved)?;
|
||||
children_resolved &= child.is_resolved();
|
||||
}
|
||||
|
||||
if children_resolved {
|
||||
drop(borrow);
|
||||
if let Node::Object { resolved, .. } = &mut *self.0.borrow_mut() {
|
||||
*resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(made_progress)
|
||||
}
|
||||
}
|
||||
Node::String { value, .. } => {
|
||||
println!("terminating at string: {}", value);
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn set_parent(&mut self, new_parent: Option<Weak<RefCell<Node>>>) {
|
||||
match self {
|
||||
Node::Reference { parent, .. }
|
||||
| Node::Object { parent, .. }
|
||||
| Node::String { parent, .. } => *parent = new_parent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
let json = serde_json::json!({
|
||||
"a": {
|
||||
"x": "$b.d"
|
||||
},
|
||||
"b": {
|
||||
"c": "$a",
|
||||
"d": "$e.f"
|
||||
},
|
||||
"e": {
|
||||
"f": "1"
|
||||
}
|
||||
});
|
||||
|
||||
dbg!(resolve(json).unwrap());
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use super::resolution::resolve_references;
|
||||
use anyhow::{Context, Result};
|
||||
use gpui::{fonts, AssetSource, FontCache};
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::{Map, Value};
|
||||
use std::{collections::HashMap, fmt, mem, sync::Arc};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use super::Theme;
|
||||
|
||||
@ -13,30 +14,6 @@ pub struct ThemeRegistry {
|
||||
font_cache: Arc<FontCache>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct KeyPathReferenceSet {
|
||||
references: Vec<KeyPathReference>,
|
||||
reference_ids_by_source: Vec<usize>,
|
||||
reference_ids_by_target: Vec<usize>,
|
||||
dependencies: Vec<(usize, usize)>,
|
||||
dependency_counts: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct KeyPathReference {
|
||||
target: KeyPath,
|
||||
source: KeyPath,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct KeyPath(Vec<Key>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Key {
|
||||
Array(usize),
|
||||
Object(String),
|
||||
}
|
||||
|
||||
impl ThemeRegistry {
|
||||
pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
@ -111,41 +88,15 @@ impl ThemeRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
let mut theme_data = Value::Object(theme_data);
|
||||
|
||||
// Find all of the key path references in the object, and then sort them according
|
||||
// to their dependencies.
|
||||
if evaluate_references {
|
||||
let mut key_path = KeyPath::default();
|
||||
let mut references = KeyPathReferenceSet::default();
|
||||
for (key, value) in theme_data.iter() {
|
||||
key_path.0.push(Key::Object(key.clone()));
|
||||
find_references(value, &mut key_path, &mut references);
|
||||
key_path.0.pop();
|
||||
}
|
||||
let sorted_references = references
|
||||
.top_sort()
|
||||
.map_err(|key_paths| anyhow!("cycle for key paths: {:?}", key_paths))?;
|
||||
|
||||
// Now update objects to include the fields of objects they extend
|
||||
for KeyPathReference { source, target } in sorted_references {
|
||||
if let Some(source) = value_at(&mut theme_data, &source).cloned() {
|
||||
let target = value_at(&mut theme_data, &target).unwrap();
|
||||
if let Value::Object(target_object) = target.take() {
|
||||
if let Value::Object(mut source_object) = source {
|
||||
deep_merge_json(&mut source_object, target_object);
|
||||
*target = Value::Object(source_object);
|
||||
} else {
|
||||
Err(anyhow!("extended key path {} is not an object", source))?;
|
||||
}
|
||||
} else {
|
||||
*target = source;
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("invalid key path '{}'", source))?;
|
||||
}
|
||||
}
|
||||
theme_data = resolve_references(theme_data)?;
|
||||
}
|
||||
|
||||
let result = Arc::new(Value::Object(theme_data));
|
||||
let result = Arc::new(theme_data);
|
||||
self.theme_data
|
||||
.lock()
|
||||
.insert(name.to_string(), result.clone());
|
||||
@ -154,311 +105,6 @@ impl ThemeRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPathReferenceSet {
|
||||
fn insert(&mut self, reference: KeyPathReference) {
|
||||
let id = self.references.len();
|
||||
let source_ix = self
|
||||
.reference_ids_by_source
|
||||
.binary_search_by_key(&&reference.source, |id| &self.references[*id].source)
|
||||
.unwrap_or_else(|i| i);
|
||||
let target_ix = self
|
||||
.reference_ids_by_target
|
||||
.binary_search_by_key(&&reference.target, |id| &self.references[*id].target)
|
||||
.unwrap_or_else(|i| i);
|
||||
|
||||
self.populate_dependencies(id, &reference);
|
||||
self.reference_ids_by_source.insert(source_ix, id);
|
||||
self.reference_ids_by_target.insert(target_ix, id);
|
||||
self.references.push(reference);
|
||||
}
|
||||
|
||||
fn top_sort(mut self) -> Result<Vec<KeyPathReference>, Vec<KeyPath>> {
|
||||
let mut results = Vec::with_capacity(self.references.len());
|
||||
let mut root_ids = Vec::with_capacity(self.references.len());
|
||||
|
||||
// Find the initial set of references that have no dependencies.
|
||||
for (id, dep_count) in self.dependency_counts.iter().enumerate() {
|
||||
if *dep_count == 0 {
|
||||
root_ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
while results.len() < root_ids.len() {
|
||||
// Just to guarantee a stable result when the inputs are randomized,
|
||||
// sort references lexicographically in absence of any dependency relationship.
|
||||
root_ids[results.len()..].sort_by_key(|id| &self.references[*id]);
|
||||
|
||||
let root_id = root_ids[results.len()];
|
||||
let root = mem::take(&mut self.references[root_id]);
|
||||
results.push(root);
|
||||
|
||||
// Remove this reference as a dependency from any of its dependent references.
|
||||
if let Ok(dep_ix) = self
|
||||
.dependencies
|
||||
.binary_search_by_key(&root_id, |edge| edge.0)
|
||||
{
|
||||
let mut first_dep_ix = dep_ix;
|
||||
let mut last_dep_ix = dep_ix + 1;
|
||||
while first_dep_ix > 0 && self.dependencies[first_dep_ix - 1].0 == root_id {
|
||||
first_dep_ix -= 1;
|
||||
}
|
||||
while last_dep_ix < self.dependencies.len()
|
||||
&& self.dependencies[last_dep_ix].0 == root_id
|
||||
{
|
||||
last_dep_ix += 1;
|
||||
}
|
||||
|
||||
// If any reference no longer has any dependencies, then then mark it as a root.
|
||||
// Preserve the references' original order where possible.
|
||||
for (_, successor_id) in self.dependencies.drain(first_dep_ix..last_dep_ix) {
|
||||
self.dependency_counts[successor_id] -= 1;
|
||||
if self.dependency_counts[successor_id] == 0 {
|
||||
root_ids.push(successor_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any references never became roots, then there are reference cycles
|
||||
// in the set. Return an error containing all of the key paths that are
|
||||
// directly involved in cycles.
|
||||
if results.len() < self.references.len() {
|
||||
let mut cycle_ref_ids = (0..self.references.len())
|
||||
.filter(|id| !root_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Iteratively remove any references that have no dependencies,
|
||||
// so that the error will only indicate which key paths are directly
|
||||
// involved in the cycles.
|
||||
let mut done = false;
|
||||
while !done {
|
||||
done = true;
|
||||
cycle_ref_ids.retain(|id| {
|
||||
if self.dependencies.iter().any(|dep| dep.0 == *id) {
|
||||
true
|
||||
} else {
|
||||
done = false;
|
||||
self.dependencies.retain(|dep| dep.1 != *id);
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut cycle_key_paths = Vec::new();
|
||||
for id in cycle_ref_ids {
|
||||
let reference = &self.references[id];
|
||||
cycle_key_paths.push(reference.target.clone());
|
||||
cycle_key_paths.push(reference.source.clone());
|
||||
}
|
||||
cycle_key_paths.sort_unstable();
|
||||
return Err(cycle_key_paths);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn populate_dependencies(&mut self, new_id: usize, new_reference: &KeyPathReference) {
|
||||
self.dependency_counts.push(0);
|
||||
|
||||
// If an existing reference's source path starts with the new reference's
|
||||
// target path, then insert this new reference before that existing reference.
|
||||
for id in Self::reference_ids_for_key_path(
|
||||
&new_reference.target.0,
|
||||
&self.references,
|
||||
&self.reference_ids_by_source,
|
||||
KeyPathReference::source,
|
||||
KeyPath::starts_with,
|
||||
) {
|
||||
Self::add_dependency(
|
||||
(new_id, id),
|
||||
&mut self.dependencies,
|
||||
&mut self.dependency_counts,
|
||||
);
|
||||
}
|
||||
|
||||
// If an existing reference's target path starts with the new reference's
|
||||
// source path, then insert this new reference after that existing reference.
|
||||
for id in Self::reference_ids_for_key_path(
|
||||
&new_reference.source.0,
|
||||
&self.references,
|
||||
&self.reference_ids_by_target,
|
||||
KeyPathReference::target,
|
||||
KeyPath::starts_with,
|
||||
) {
|
||||
Self::add_dependency(
|
||||
(id, new_id),
|
||||
&mut self.dependencies,
|
||||
&mut self.dependency_counts,
|
||||
);
|
||||
}
|
||||
|
||||
// If an existing reference's source path is a prefix of the new reference's
|
||||
// target path, then insert this new reference before that existing reference.
|
||||
for prefix in new_reference.target.prefixes() {
|
||||
for id in Self::reference_ids_for_key_path(
|
||||
prefix,
|
||||
&self.references,
|
||||
&self.reference_ids_by_source,
|
||||
KeyPathReference::source,
|
||||
PartialEq::eq,
|
||||
) {
|
||||
Self::add_dependency(
|
||||
(new_id, id),
|
||||
&mut self.dependencies,
|
||||
&mut self.dependency_counts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If an existing reference's target path is a prefix of the new reference's
|
||||
// source path, then insert this new reference after that existing reference.
|
||||
for prefix in new_reference.source.prefixes() {
|
||||
for id in Self::reference_ids_for_key_path(
|
||||
prefix,
|
||||
&self.references,
|
||||
&self.reference_ids_by_target,
|
||||
KeyPathReference::target,
|
||||
PartialEq::eq,
|
||||
) {
|
||||
Self::add_dependency(
|
||||
(id, new_id),
|
||||
&mut self.dependencies,
|
||||
&mut self.dependency_counts,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If an existing reference's target path is a prefix of the new reference's target path,
|
||||
// then insert this new reference before that existing reference.
|
||||
for prefix in new_reference.target.prefixes() {
|
||||
for id in Self::reference_ids_for_key_path(
|
||||
prefix,
|
||||
&self.references,
|
||||
&self.reference_ids_by_target,
|
||||
KeyPathReference::target,
|
||||
KeyPath::starts_with,
|
||||
) {
|
||||
Self::add_dependency(
|
||||
(new_id, id),
|
||||
&mut self.dependencies,
|
||||
&mut self.dependency_counts,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all existing references that satisfy a given predicate with respect
|
||||
// to a given key path. Use a sorted array of reference ids in order to avoid
|
||||
// performing unnecessary comparisons.
|
||||
fn reference_ids_for_key_path<'a>(
|
||||
key_path: &[Key],
|
||||
references: &[KeyPathReference],
|
||||
sorted_reference_ids: &'a [usize],
|
||||
reference_attribute: impl Fn(&KeyPathReference) -> &KeyPath,
|
||||
predicate: impl Fn(&KeyPath, &[Key]) -> bool,
|
||||
) -> impl Iterator<Item = usize> + 'a {
|
||||
let ix = sorted_reference_ids
|
||||
.binary_search_by_key(&key_path, |id| &reference_attribute(&references[*id]).0)
|
||||
.unwrap_or_else(|i| i);
|
||||
|
||||
let mut start_ix = ix;
|
||||
while start_ix > 0 {
|
||||
let reference_id = sorted_reference_ids[start_ix - 1];
|
||||
let reference = &references[reference_id];
|
||||
if !predicate(&reference_attribute(reference), key_path) {
|
||||
break;
|
||||
}
|
||||
start_ix -= 1;
|
||||
}
|
||||
|
||||
let mut end_ix = ix;
|
||||
while end_ix < sorted_reference_ids.len() {
|
||||
let reference_id = sorted_reference_ids[end_ix];
|
||||
let reference = &references[reference_id];
|
||||
if !predicate(&reference_attribute(reference), key_path) {
|
||||
break;
|
||||
}
|
||||
end_ix += 1;
|
||||
}
|
||||
|
||||
sorted_reference_ids[start_ix..end_ix].iter().copied()
|
||||
}
|
||||
|
||||
fn add_dependency(
|
||||
(predecessor, successor): (usize, usize),
|
||||
dependencies: &mut Vec<(usize, usize)>,
|
||||
dependency_counts: &mut Vec<usize>,
|
||||
) {
|
||||
let dependency = (predecessor, successor);
|
||||
if let Err(i) = dependencies.binary_search(&dependency) {
|
||||
dependencies.insert(i, dependency);
|
||||
}
|
||||
dependency_counts[successor] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPathReference {
|
||||
fn source(&self) -> &KeyPath {
|
||||
&self.source
|
||||
}
|
||||
|
||||
fn target(&self) -> &KeyPath {
|
||||
&self.target
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPath {
|
||||
fn new(string: &str) -> Self {
|
||||
Self(
|
||||
string
|
||||
.split(".")
|
||||
.map(|key| Key::Object(key.to_string()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn starts_with(&self, other: &[Key]) -> bool {
|
||||
self.0.starts_with(&other)
|
||||
}
|
||||
|
||||
fn prefixes(&self) -> impl Iterator<Item = &[Key]> {
|
||||
(1..self.0.len()).map(move |end_ix| &self.0[0..end_ix])
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<[Key]> for KeyPath {
|
||||
fn eq(&self, other: &[Key]) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for KeyPathReference {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"KeyPathReference {{ {} <- {} }}",
|
||||
self.target, self.source
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, key) in self.0.iter().enumerate() {
|
||||
match key {
|
||||
Key::Array(index) => write!(f, "[{}]", index)?,
|
||||
Key::Object(key) => {
|
||||
if i > 0 {
|
||||
".".fmt(f)?;
|
||||
}
|
||||
key.fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn deep_merge_json(base: &mut Map<String, Value>, extension: Map<String, Value>) {
|
||||
for (key, extension_value) in extension {
|
||||
if let Value::Object(extension_object) = extension_value {
|
||||
@ -473,69 +119,12 @@ fn deep_merge_json(base: &mut Map<String, Value>, extension: Map<String, Value>)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_references(value: &Value, key_path: &mut KeyPath, references: &mut KeyPathReferenceSet) {
|
||||
match value {
|
||||
Value::Array(vec) => {
|
||||
for (ix, value) in vec.iter().enumerate() {
|
||||
key_path.0.push(Key::Array(ix));
|
||||
find_references(value, key_path, references);
|
||||
key_path.0.pop();
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
for (key, value) in map.iter() {
|
||||
if key == "extends" {
|
||||
if let Some(source_path) = value.as_str().and_then(|s| s.strip_prefix("$")) {
|
||||
references.insert(KeyPathReference {
|
||||
source: KeyPath::new(source_path),
|
||||
target: key_path.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
key_path.0.push(Key::Object(key.to_string()));
|
||||
find_references(value, key_path, references);
|
||||
key_path.0.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::String(string) => {
|
||||
if let Some(source_path) = string.strip_prefix("$") {
|
||||
references.insert(KeyPathReference {
|
||||
source: KeyPath::new(source_path),
|
||||
target: key_path.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn value_at<'a>(object: &'a mut Map<String, Value>, key_path: &KeyPath) -> Option<&'a mut Value> {
|
||||
let mut key_path = key_path.0.iter();
|
||||
if let Some(Key::Object(first_key)) = key_path.next() {
|
||||
let mut cur_value = object.get_mut(first_key);
|
||||
for key in key_path {
|
||||
if let Some(value) = cur_value {
|
||||
match key {
|
||||
Key::Array(ix) => cur_value = value.get_mut(ix),
|
||||
Key::Object(key) => cur_value = value.get_mut(key),
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
cur_value
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{test::test_app_state, theme::DEFAULT_THEME_NAME};
|
||||
use anyhow::anyhow;
|
||||
use gpui::MutableAppContext;
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_bundled_themes(cx: &mut MutableAppContext) {
|
||||
@ -593,6 +182,12 @@ mod tests {
|
||||
|
||||
let registry = ThemeRegistry::new(assets, cx.font_cache().clone());
|
||||
let theme_data = registry.load("light", true).unwrap();
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(theme_data.as_ref()).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
theme_data.as_ref(),
|
||||
&serde_json::json!({
|
||||
@ -669,120 +264,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_path_reference_set_simple() {
|
||||
let input_references = build_refs(&[
|
||||
("r", "a"),
|
||||
("a.b.c", "d"),
|
||||
("d.e", "f"),
|
||||
("t.u", "v"),
|
||||
("v.w", "x"),
|
||||
("v.y", "x"),
|
||||
("d.h", "i"),
|
||||
("v.z", "x"),
|
||||
("f.g", "d.h"),
|
||||
]);
|
||||
let expected_references = build_refs(&[
|
||||
("d.h", "i"),
|
||||
("f.g", "d.h"),
|
||||
("d.e", "f"),
|
||||
("a.b.c", "d"),
|
||||
("r", "a"),
|
||||
("v.w", "x"),
|
||||
("v.y", "x"),
|
||||
("v.z", "x"),
|
||||
("t.u", "v"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut reference_set = KeyPathReferenceSet::default();
|
||||
for reference in input_references {
|
||||
reference_set.insert(reference);
|
||||
}
|
||||
assert_eq!(reference_set.top_sort().unwrap(), expected_references);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_path_reference_set_with_cycles() {
|
||||
let input_references = build_refs(&[
|
||||
("x", "a.b"),
|
||||
("y", "x.c"),
|
||||
("a.b.c", "d.e"),
|
||||
("d.e.f", "g.h"),
|
||||
("g.h.i", "a"),
|
||||
]);
|
||||
|
||||
let mut reference_set = KeyPathReferenceSet::default();
|
||||
for reference in input_references {
|
||||
reference_set.insert(reference);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
reference_set.top_sort().unwrap_err(),
|
||||
&[
|
||||
KeyPath::new("a"),
|
||||
KeyPath::new("a.b.c"),
|
||||
KeyPath::new("d.e"),
|
||||
KeyPath::new("d.e.f"),
|
||||
KeyPath::new("g.h"),
|
||||
KeyPath::new("g.h.i"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 20)]
|
||||
async fn test_key_path_reference_set_random(mut rng: StdRng) {
|
||||
let examples: &[&[_]] = &[
|
||||
// &[
|
||||
// ("n.d.h", "i"),
|
||||
// ("f.g", "n.d.h"),
|
||||
// ("n.d.e", "f"),
|
||||
// ("a.b.c", "n.d"),
|
||||
// ("r", "a"),
|
||||
// ("q.q.q", "r.s"),
|
||||
// ("r.t", "q"),
|
||||
// ("x.x", "r.r"),
|
||||
// ("v.w", "x"),
|
||||
// ("v.y", "x"),
|
||||
// ("v.z", "x"),
|
||||
// ("t.u", "v"),
|
||||
// ],
|
||||
&[
|
||||
("w.x.y.z", "t.u.z"),
|
||||
("x", "w.x"),
|
||||
("a.b.c1", "x.b1.c"),
|
||||
("a.b.c2", "x.b2.c"),
|
||||
],
|
||||
&[
|
||||
("x.y.z", "m.n.n.o.p"),
|
||||
("x.y", "m.n.n.o.q"),
|
||||
("u.v.w", "x.y.z"),
|
||||
("a.b.c.d.e", "u.v"),
|
||||
("a.b.c.d.f", "u.v"),
|
||||
("a.b.c.d.g", "u.v"),
|
||||
("a.b.c.d", "u.v"),
|
||||
],
|
||||
];
|
||||
|
||||
for example in examples {
|
||||
let expected_references = build_refs(example).collect::<Vec<_>>();
|
||||
let mut input_references = expected_references.clone();
|
||||
input_references.sort_by_key(|_| rng.gen_range(0..1000));
|
||||
let mut reference_set = KeyPathReferenceSet::default();
|
||||
for reference in input_references {
|
||||
reference_set.insert(reference);
|
||||
}
|
||||
assert_eq!(reference_set.top_sort().unwrap(), expected_references);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_refs<'a>(rows: &'a [(&str, &str)]) -> impl Iterator<Item = KeyPathReference> + 'a {
|
||||
rows.iter().map(|(target, source)| KeyPathReference {
|
||||
target: KeyPath::new(target),
|
||||
source: KeyPath::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
struct TestAssets(&'static [(&'static str, &'static str)]);
|
||||
|
||||
impl AssetSource for TestAssets {
|
||||
|
Loading…
Reference in New Issue
Block a user