mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 21:25:16 +00:00
Implement glob ignores (#272)
* Add Glob wrapper type with better serialization * Introduce PathIgnoreRule struct * Implement equality for Glob type * Add PathIgnoreRule to InstanceContext * Implement glob ignores in directory middleware * Fix up filters * Use Iterator::all instead of loop * Add project-level configuration for glob ignores * Add test project for glob ignores * Wire up project file and snapshots to make glob ignores work * Better codepaths for adding ignore rules with empty iterators * Add test for globs inherited from parent projects * Add test details, support glob ignores in nested projects * Add feature flag for globs * Switch to use ExactSizeIterator instead of size_hint * Remove glob visitor
This commit is contained in:
committed by
GitHub
parent
ae811aafd0
commit
e261e7a2c7
@@ -8,7 +8,8 @@ use rbx_dom_weak::RbxInstanceProperties;
|
||||
use crate::{
|
||||
project::Project,
|
||||
snapshot::{
|
||||
apply_patch_set, compute_patch_set, InstanceContext, InstancePropertiesWithMeta, RojoTree,
|
||||
apply_patch_set, compute_patch_set, InstanceContext, InstancePropertiesWithMeta,
|
||||
PathIgnoreRule, RojoTree,
|
||||
},
|
||||
snapshot_middleware::snapshot_from_vfs,
|
||||
vfs::{Vfs, VfsFetcher},
|
||||
@@ -38,8 +39,19 @@ pub fn start<F: VfsFetcher>(
|
||||
.get(fuzzy_project_path)
|
||||
.expect("could not get project path");
|
||||
|
||||
let mut instance_context = InstanceContext::default();
|
||||
|
||||
if let Some(project) = &maybe_project {
|
||||
let rules = project.glob_ignore_paths.iter().map(|glob| PathIgnoreRule {
|
||||
glob: glob.clone(),
|
||||
base_path: project.folder_location().to_path_buf(),
|
||||
});
|
||||
|
||||
instance_context.add_path_ignore_rules(rules);
|
||||
}
|
||||
|
||||
log::trace!("Generating snapshot of instances from VFS");
|
||||
let snapshot = snapshot_from_vfs(&InstanceContext::default(), vfs, &entry)
|
||||
let snapshot = snapshot_from_vfs(&instance_context, vfs, &entry)
|
||||
.expect("snapshot failed")
|
||||
.expect("snapshot did not return an instance");
|
||||
|
||||
|
||||
50
src/glob.rs
Normal file
50
src/glob.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
//! Wrapper around globset's Glob type that has better serialization
|
||||
//! characteristics by coupling Glob and GlobMatcher into a single type.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use globset::{Glob as InnerGlob, GlobMatcher};
|
||||
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub use globset::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Glob {
|
||||
inner: InnerGlob,
|
||||
matcher: GlobMatcher,
|
||||
}
|
||||
|
||||
impl Glob {
|
||||
pub fn new(glob: &str) -> Result<Self, Error> {
|
||||
let inner = InnerGlob::new(glob)?;
|
||||
let matcher = inner.compile_matcher();
|
||||
|
||||
Ok(Glob { inner, matcher })
|
||||
}
|
||||
|
||||
pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
self.matcher.is_match(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Glob {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Glob {}
|
||||
|
||||
impl Serialize for Glob {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(self.inner.glob())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Glob {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let glob = <&str as Deserialize>::deserialize(deserializer)?;
|
||||
|
||||
Glob::new(glob).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ mod tree_view;
|
||||
mod auth_cookie;
|
||||
mod change_processor;
|
||||
mod common_setup;
|
||||
mod glob;
|
||||
mod message_queue;
|
||||
mod multimap;
|
||||
mod path_map;
|
||||
|
||||
@@ -8,6 +8,8 @@ use rbx_dom_weak::UnresolvedRbxValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use crate::glob::Glob;
|
||||
|
||||
static PROJECT_FILENAME: &str = "default.project.json";
|
||||
|
||||
/// Error type returned by any function that handles projects.
|
||||
@@ -52,6 +54,12 @@ pub struct Project {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub serve_place_ids: Option<HashSet<u64>>,
|
||||
|
||||
/// A list of globs, relative to the folder the project file is in, that
|
||||
/// match files that should be excluded if Rojo encounters them.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[cfg_attr(not(feature = "unstable_glob_ignore_paths"), serde(skip))]
|
||||
pub glob_ignore_paths: Vec<Glob>,
|
||||
|
||||
/// The path to the file that this project came from. Relative paths in the
|
||||
/// project should be considered relative to the parent of this field, also
|
||||
/// given by `Project::folder_location`.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{path_serializer, project::ProjectNode};
|
||||
use crate::{glob::Glob, path_serializer, project::ProjectNode};
|
||||
|
||||
/// Rojo-specific metadata that can be associated with an instance or a snapshot
|
||||
/// of an instance.
|
||||
@@ -99,11 +100,59 @@ impl Default for InstanceMetadata {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct InstanceContext {}
|
||||
pub struct InstanceContext {
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ignore_rules: Arc<Vec<PathIgnoreRule>>,
|
||||
}
|
||||
|
||||
impl InstanceContext {
|
||||
/// Extend the list of ignore rules in the context with the given new rules.
|
||||
pub fn add_path_ignore_rules<I>(&mut self, new_rules: I)
|
||||
where
|
||||
I: IntoIterator<Item = PathIgnoreRule>,
|
||||
I::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
let new_rules = new_rules.into_iter();
|
||||
|
||||
// If the iterator is empty, we can skip cloning our list of ignore
|
||||
// rules and appending to it.
|
||||
if new_rules.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let rules = Arc::make_mut(&mut self.path_ignore_rules);
|
||||
rules.extend(new_rules);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InstanceContext {
|
||||
fn default() -> Self {
|
||||
InstanceContext {}
|
||||
InstanceContext {
|
||||
path_ignore_rules: Arc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PathIgnoreRule {
|
||||
/// The path that this glob is relative to. Since ignore globs are defined
|
||||
/// in project files, this will generally be the folder containing the
|
||||
/// project file that defined this glob.
|
||||
#[serde(serialize_with = "path_serializer::serialize_absolute")]
|
||||
pub base_path: PathBuf,
|
||||
|
||||
/// The actual glob that can be matched against the input path.
|
||||
pub glob: Glob,
|
||||
}
|
||||
|
||||
impl PathIgnoreRule {
|
||||
pub fn passes<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
let path = path.as_ref();
|
||||
|
||||
match path.strip_prefix(&self.base_path) {
|
||||
Ok(suffix) => !self.glob.is_match(suffix),
|
||||
Err(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,16 @@ impl SnapshotMiddleware for SnapshotDir {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let children: Vec<VfsEntry> = entry.children(vfs)?;
|
||||
let passes_filter_rules = |child: &VfsEntry| {
|
||||
context
|
||||
.path_ignore_rules
|
||||
.iter()
|
||||
.all(|rule| rule.passes(child.path()))
|
||||
};
|
||||
|
||||
let mut snapshot_children = Vec::new();
|
||||
|
||||
for child in children.into_iter() {
|
||||
for child in entry.children(vfs)?.into_iter().filter(passes_filter_rules) {
|
||||
if let Some(child_snapshot) = snapshot_from_vfs(context, vfs, &child)? {
|
||||
snapshot_children.push(child_snapshot);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use rbx_reflection::try_resolve_value;
|
||||
|
||||
use crate::{
|
||||
project::{Project, ProjectNode},
|
||||
snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot, InstigatingSource},
|
||||
snapshot::{
|
||||
InstanceContext, InstanceMetadata, InstanceSnapshot, InstigatingSource, PathIgnoreRule,
|
||||
},
|
||||
vfs::{FsResultExt, Vfs, VfsEntry, VfsFetcher},
|
||||
};
|
||||
|
||||
@@ -45,10 +47,19 @@ impl SnapshotMiddleware for SnapshotProject {
|
||||
let project = Project::load_from_slice(&entry.contents(vfs)?, entry.path())
|
||||
.map_err(|err| SnapshotError::malformed_project(err, entry.path()))?;
|
||||
|
||||
let mut context = context.clone();
|
||||
|
||||
let rules = project.glob_ignore_paths.iter().map(|glob| PathIgnoreRule {
|
||||
glob: glob.clone(),
|
||||
base_path: project.folder_location().to_path_buf(),
|
||||
});
|
||||
|
||||
context.add_path_ignore_rules(rules);
|
||||
|
||||
// Snapshotting a project should always return an instance, so this
|
||||
// unwrap is safe.
|
||||
let mut snapshot = snapshot_project_node(
|
||||
context,
|
||||
&context,
|
||||
project.folder_location(),
|
||||
&project.name,
|
||||
&project.tree,
|
||||
|
||||
Reference in New Issue
Block a user