Skip to content

Commit

Permalink
#47: settings and assemble api seems good
Browse files Browse the repository at this point in the history
  • Loading branch information
joshradin committed Oct 10, 2022
1 parent d20c526 commit 35c1559
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/assemble-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ merge = { version = "0.1.0", features = ["derive"] }
ron-serde = { package = "ron", version = "0.8.0", optional = true }
rmp-serde = { version = "1.1.1", optional = true }
parking_lot = { version = "0.12.1", features = ["deadlock_detection"] }
ptree = { version = "0.4.0", features = ["petgraph"] }

[dev-dependencies]
assemble-macros = { path = "../assemble-macros" }
Expand Down
2 changes: 2 additions & 0 deletions crates/assemble-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub mod prelude {
#[cfg(feature = "unstable")]
pub use unstable::enabled::prelude::*;

pub use startup_api::{invocation::*, initialization::*, listeners};

pub use crate::project::error::ProjectError;
pub use crate::project::error::ProjectResult;
pub use identifier::{ProjectId, TaskId};
Expand Down
3 changes: 3 additions & 0 deletions crates/assemble-core/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ pub trait Plugin<T: ?Sized>: Default {
}
}

/// Some value that can have plugins applied to it.
pub trait PluginAware: Sized {
/// Apply a plugin to this.
fn apply_plugin<P: Plugin<Self>>(&mut self) -> ProjectResult {
let ref mut manager = self.plugin_manager().clone();
manager.apply::<P>(self)
}

/// Gets a reference to the plugin manager for this value.
fn plugin_manager(&self) -> &PluginManager<Self>;
/// Gets a mutable reference to the plugin manager for this value.
fn plugin_manager_mut(&mut self) -> &mut PluginManager<Self>;
}

Expand Down
315 changes: 314 additions & 1 deletion crates/assemble-core/src/startup_api/initialization/descriptor.rs
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());
}
}
Loading

0 comments on commit 35c1559

Please sign in to comment.