-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#47: settings and assemble api seems good
- Loading branch information
Showing
6 changed files
with
409 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
315 changes: 314 additions & 1 deletion
315
crates/assemble-core/src/startup_api/initialization/descriptor.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,317 @@ | ||
use crate::Project; | ||
use petgraph::graph::DefaultIx; | ||
use petgraph::prelude::*; | ||
use petgraph::visit::IntoNodeReferences; | ||
use ptree::PrintConfig; | ||
use std::fmt; | ||
use std::fmt::Write as _; | ||
use std::fmt::{Debug, Display, Formatter}; | ||
use std::io::Write as _; | ||
use std::path::{Path, PathBuf}; | ||
|
||
/// A project descriptor is used to define projects. | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct ProjectDescriptor { | ||
build_file: ProjectDescriptorLocation, | ||
name: String, | ||
} | ||
|
||
impl Display for ProjectDescriptor { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
write!(f, "{} ({:?})", self.name, self.build_file) | ||
} | ||
} | ||
|
||
impl ProjectDescriptor { | ||
/// creates a new project descriptor | ||
fn new<S: AsRef<str>>(name: S, build_file: ProjectDescriptorLocation) -> Self { | ||
let name = name.as_ref().to_string(); | ||
Self { build_file, name } | ||
} | ||
|
||
/// if this only has the directory known, this sets the file name | ||
fn set_file_name(&mut self, file_name: &str) { | ||
let file_path = if let ProjectDescriptorLocation::KnownDirectory(dir) = &self.build_file { | ||
dir.join(file_name) | ||
} else { | ||
return; | ||
}; | ||
|
||
self.build_file = ProjectDescriptorLocation::KnownFile(file_path); | ||
} | ||
|
||
/// Gets the name of the project | ||
pub fn name(&self) -> &str { | ||
&self.name | ||
} | ||
|
||
/// Sets the name of the project | ||
pub fn set_name(&mut self, name: impl AsRef<str>) { | ||
self.name = name.as_ref().to_string(); | ||
} | ||
|
||
/// Gets the build file associated with this project, if known | ||
pub fn build_file(&self) -> Option<&Path> { | ||
match &self.build_file { | ||
ProjectDescriptorLocation::KnownFile(f) => Some(&*f), | ||
ProjectDescriptorLocation::KnownDirectory(_) => None, | ||
} | ||
} | ||
|
||
/// Checks if this project descriptor is contained in this directory | ||
pub fn matches_dir(&self, path: impl AsRef<Path>) -> bool { | ||
let path = path.as_ref(); | ||
match &self.build_file { | ||
ProjectDescriptorLocation::KnownFile(f) => match f.parent() { | ||
Some(parent) => parent.ends_with(path), | ||
None => path == Path::new(""), | ||
}, | ||
ProjectDescriptorLocation::KnownDirectory(d) => d.ends_with(path), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Eq, PartialEq)] | ||
enum ProjectDescriptorLocation { | ||
KnownFile(PathBuf), | ||
KnownDirectory(PathBuf), | ||
} | ||
|
||
impl Debug for ProjectDescriptorLocation { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
match self { | ||
ProjectDescriptorLocation::KnownFile(file) => { | ||
write!(f, "{:?}", file) | ||
} | ||
ProjectDescriptorLocation::KnownDirectory(d) => { | ||
write!(f, "{:?}", d.join("?")) | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Describes the project graph | ||
#[derive(Debug)] | ||
pub struct ProjectGraph { | ||
graph: DiGraph<ProjectDescriptor, ()>, | ||
project_dir: PathBuf, | ||
root_project: NodeIndex, | ||
default_build_script_file: Option<String>, | ||
} | ||
|
||
impl ProjectGraph { | ||
/// Creates a new project graph with a pre-initialized root project | ||
pub(crate) fn new<P: AsRef<Path>>(project_dir: P) -> Self { | ||
let project_name = project_dir | ||
.as_ref() | ||
.file_name() | ||
.unwrap_or_else(|| panic!("{:?} has no file name", project_dir.as_ref())) | ||
.to_str() | ||
.unwrap_or_else(|| { | ||
panic!( | ||
"{:?} file name can not be represented as a utf-8 string", | ||
project_dir.as_ref() | ||
) | ||
}); | ||
|
||
let root_project = ProjectDescriptor::new( | ||
project_name, | ||
ProjectDescriptorLocation::KnownDirectory(project_dir.as_ref().to_path_buf()), | ||
); | ||
let mut graph = DiGraph::new(); | ||
let idx = graph.add_node(root_project); | ||
Self { | ||
graph, | ||
project_dir: project_dir.as_ref().to_path_buf(), | ||
root_project: idx, | ||
default_build_script_file: None, | ||
} | ||
} | ||
|
||
/// Gets the root project descriptor | ||
pub fn root_project(&self) -> &ProjectDescriptor { | ||
&self.graph[self.root_project] | ||
} | ||
|
||
/// Gets a mutable reference to the root project descriptor | ||
pub fn root_project_mut(&mut self) -> &mut ProjectDescriptor { | ||
&mut self.graph[self.root_project] | ||
} | ||
|
||
pub fn set_default_build_file_name(&mut self, name: &str) { | ||
if self.default_build_script_file.is_some() { | ||
panic!( | ||
"default build script file name already set to {:?}", | ||
self.default_build_script_file.as_ref().unwrap() | ||
); | ||
} | ||
|
||
self.default_build_script_file = Some(name.to_string()); | ||
for node in self.graph.node_indices() { | ||
self.graph[node].set_file_name(name); | ||
} | ||
} | ||
|
||
/// Adds a child project to the root project | ||
pub fn project<S: AsRef<str>, F: FnOnce(&mut ProjectBuilder)>( | ||
&mut self, | ||
path: S, | ||
configure: F, | ||
) { | ||
let path = path.as_ref(); | ||
let mut builder = ProjectBuilder::new(&self.project_dir, path.to_string()); | ||
(configure)(&mut builder); | ||
self.add_project_from_builder(self.root_project, builder); | ||
} | ||
|
||
/// Adds a child project to some other project | ||
fn add_project_from_builder(&mut self, parent: NodeIndex, builder: ProjectBuilder) { | ||
let ProjectBuilder { | ||
name, | ||
dir, | ||
children, | ||
} = builder; | ||
|
||
let location = match &self.default_build_script_file { | ||
None => ProjectDescriptorLocation::KnownDirectory(dir), | ||
Some(s) => ProjectDescriptorLocation::KnownFile(dir.join(s)), | ||
}; | ||
let pd = ProjectDescriptor::new(name, location); | ||
|
||
let node = self.graph.add_node(pd); | ||
self.graph.add_edge(parent, node, ()); | ||
|
||
for child_builder in children { | ||
self.add_project_from_builder(node, child_builder); | ||
} | ||
} | ||
|
||
/// Find a project by path | ||
pub fn find_project<P: AsRef<Path>>(&self, path: P) -> Option<&ProjectDescriptor> { | ||
self.graph | ||
.node_indices() | ||
.find(|&idx| self.graph[idx].matches_dir(&path)) | ||
.map(|idx| &self.graph[idx]) | ||
} | ||
|
||
/// Find a project by path | ||
pub fn find_project_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut ProjectDescriptor> { | ||
self.graph | ||
.node_indices() | ||
.find(|&idx| self.graph[idx].matches_dir(&path)) | ||
.map(|idx| &mut self.graph[idx]) | ||
} | ||
} | ||
|
||
impl Display for ProjectGraph { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
let mut buffer = Vec::new(); | ||
ptree::graph::write_graph_with( | ||
&self.graph, | ||
self.root_project, | ||
&mut buffer, | ||
&PrintConfig::default(), | ||
) | ||
.map_err(|_| fmt::Error)?; | ||
let string = String::from_utf8(buffer).map_err(|_| fmt::Error)?; | ||
write!(f, "{}", string) | ||
} | ||
} | ||
|
||
/// Helps to build a project | ||
pub struct ProjectBuilder { | ||
name: String, | ||
dir: PathBuf, | ||
children: Vec<ProjectBuilder>, | ||
} | ||
|
||
impl ProjectBuilder { | ||
fn new(parent_dir: &Path, name: String) -> Self { | ||
let dir = parent_dir.join(&name); | ||
Self { | ||
name, | ||
dir, | ||
children: vec![], | ||
} | ||
} | ||
|
||
/// Sets the name of this project | ||
pub fn set_name(&mut self, name: impl AsRef<str>) { | ||
self.name = name.as_ref().to_string(); | ||
} | ||
|
||
/// Sets the directory of this project. by default, the directory is the parent projects | ||
/// directory + name | ||
pub fn set_dir(&mut self, path: impl AsRef<Path>) { | ||
self.dir = path.as_ref().to_path_buf(); | ||
} | ||
|
||
/// Adds a child project to this project. `path` is relative to this project, and should | ||
/// be written as a simple identifier | ||
pub fn project<S: AsRef<str>, F: FnOnce(&mut ProjectBuilder)>( | ||
&mut self, | ||
path: S, | ||
configure: F, | ||
) { | ||
let path = path.as_ref(); | ||
let mut builder = ProjectBuilder::new(&self.dir, path.to_string()); | ||
(configure)(&mut builder); | ||
self.children.push(builder); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::startup_api::initialization::ProjectGraph; | ||
use std::env::current_dir; | ||
use std::path::PathBuf; | ||
|
||
#[test] | ||
fn print_graph() { | ||
let path = PathBuf::from("assemble"); | ||
let mut graph = ProjectGraph::new(path); | ||
|
||
graph.project("list", |builder| { | ||
builder.project("linked", |_| {}); | ||
builder.project("array", |_| {}); | ||
}); | ||
graph.project("map", |_| {}); | ||
|
||
println!("{}", graph); | ||
} | ||
|
||
#[test] | ||
fn can_set_default_build_name() { | ||
let path = PathBuf::from("assemble"); | ||
let mut graph = ProjectGraph::new(path); | ||
graph.set_default_build_file_name("build.assemble"); | ||
|
||
println!("{}", graph); | ||
assert_eq!( | ||
graph.root_project().build_file(), | ||
Some(&*PathBuf::from_iter(["assemble", "build.assemble"])) | ||
) | ||
} | ||
|
||
#[test] | ||
fn can_find_project() { | ||
let path = PathBuf::from("assemble"); | ||
let mut graph = ProjectGraph::new(path); | ||
|
||
graph.project("list", |builder| { | ||
builder.project("linked", |_| {}); | ||
builder.project("array", |_| {}); | ||
}); | ||
graph.project("map", |b| { | ||
b.project("set", |_| {}); | ||
b.project("ordered", |_| {}); | ||
b.project("hashed", |_| {}); | ||
}); | ||
|
||
println!("graph: {:#}", graph); | ||
|
||
} | ||
assert!(graph.find_project("assemble/map/hashed").is_some()); | ||
assert!(graph.find_project("assemble/list/array").is_some()); | ||
assert!(graph.find_project("assemble/list/garfunkle").is_none()); | ||
} | ||
} |
Oops, something went wrong.