rustfmt the codebase

This commit is contained in:
Lucien Greathouse
2019-08-27 15:10:34 -07:00
parent fea303ac8b
commit 7fb9aa2115
45 changed files with 766 additions and 764 deletions

View File

@@ -1,8 +1,4 @@
use std::{ use std::{fs, path::Path, process::Command};
fs,
path::Path,
process::Command,
};
use insta::assert_snapshot_matches; use insta::assert_snapshot_matches;
use tempfile::tempdir; use tempfile::tempdir;
@@ -67,7 +63,10 @@ fn run_build_test(test_name: &str) {
let status = Command::new(exe_path) let status = Command::new(exe_path)
.args(&[ .args(&[
"build", input_path.to_str().unwrap(), "-o", output_path.to_str().unwrap(), "build",
input_path.to_str().unwrap(),
"-o",
output_path.to_str().unwrap(),
]) ])
.env("RUST_LOG", "error") .env("RUST_LOG", "error")
.current_dir(working_dir) .current_dir(working_dir)
@@ -76,8 +75,7 @@ fn run_build_test(test_name: &str) {
assert!(status.success(), "Rojo did not exit successfully"); assert!(status.success(), "Rojo did not exit successfully");
let contents = fs::read_to_string(&output_path) let contents = fs::read_to_string(&output_path).expect("Couldn't read output file");
.expect("Couldn't read output file");
assert_snapshot_matches!(test_name, contents); assert_snapshot_matches!(test_name, contents);
} }

View File

@@ -1,12 +0,0 @@
reorder_imports = true
reorder_imported_names = true
reorder_imports_in_group = true
attributes_on_same_line_as_field = false
attributes_on_same_line_as_variant = false
chain_split_single_child = true
wrap_comments = true
imports_indent = "Block"
match_block_trailing_comma = true
match_pattern_separator_break_point = "Front"
error_on_line_overflow = false
struct_lit_multiline_style = "ForceMulti"

View File

@@ -1,12 +1,11 @@
use std::{ use std::{
env, env, panic,
panic,
path::{Path, PathBuf}, path::{Path, PathBuf},
process, process,
}; };
use log::error;
use clap::{clap_app, ArgMatches}; use clap::{clap_app, ArgMatches};
use log::error;
use librojo::commands; use librojo::commands;
@@ -21,8 +20,7 @@ fn make_path_absolute(value: &Path) -> PathBuf {
fn main() { fn main() {
{ {
let log_env = env_logger::Env::default() let log_env = env_logger::Env::default().default_filter_or("warn");
.default_filter_or("warn");
env_logger::Builder::from_env(log_env) env_logger::Builder::from_env(log_env)
.default_format_timestamp(false) .default_format_timestamp(false)
@@ -95,7 +93,8 @@ fn show_crash_message(message: &str) {
} }
fn start_init(sub_matches: &ArgMatches) { fn start_init(sub_matches: &ArgMatches) {
let fuzzy_project_path = make_path_absolute(Path::new(sub_matches.value_of("PATH").unwrap_or(""))); let fuzzy_project_path =
make_path_absolute(Path::new(sub_matches.value_of("PATH").unwrap_or("")));
let kind = sub_matches.value_of("kind"); let kind = sub_matches.value_of("kind");
let options = commands::InitOptions { let options = commands::InitOptions {
@@ -104,11 +103,11 @@ fn start_init(sub_matches: &ArgMatches) {
}; };
match commands::init(&options) { match commands::init(&options) {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
process::exit(1); process::exit(1);
}, }
} }
} }
@@ -124,7 +123,7 @@ fn start_serve(sub_matches: &ArgMatches) {
Err(_) => { Err(_) => {
error!("Invalid port {}", v); error!("Invalid port {}", v);
process::exit(1); process::exit(1);
}, }
}, },
None => None, None => None,
}; };
@@ -135,11 +134,11 @@ fn start_serve(sub_matches: &ArgMatches) {
}; };
match commands::serve(&options) { match commands::serve(&options) {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
process::exit(1); process::exit(1);
}, }
} }
} }
@@ -158,11 +157,11 @@ fn start_build(sub_matches: &ArgMatches) {
}; };
match commands::build(&options) { match commands::build(&options) {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
process::exit(1); process::exit(1);
}, }
} }
} }
@@ -183,7 +182,7 @@ fn start_upload(sub_matches: &ArgMatches) {
Err(_) => { Err(_) => {
error!("Invalid place ID {}", arg); error!("Invalid place ID {}", arg);
process::exit(1); process::exit(1);
}, }
} }
}; };
@@ -195,10 +194,10 @@ fn start_upload(sub_matches: &ArgMatches) {
}; };
match commands::upload(&options) { match commands::upload(&options) {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
process::exit(1); process::exit(1);
}, }
} }
} }

View File

@@ -1,15 +1,15 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::File, fs::File,
io::{self, Write, BufWriter}, io::{self, BufWriter, Write},
path::PathBuf, path::PathBuf,
}; };
use rbx_dom_weak::{RbxTree, RbxInstanceProperties};
use failure::Fail; use failure::Fail;
use rbx_dom_weak::{RbxInstanceProperties, RbxTree};
use crate::{ use crate::{
imfs::new::{Imfs, RealFetcher, WatchMode, FsError}, imfs::new::{FsError, Imfs, RealFetcher, WatchMode},
snapshot::{apply_patch_set, compute_patch_set}, snapshot::{apply_patch_set, compute_patch_set},
snapshot_middleware::snapshot_from_imfs, snapshot_middleware::snapshot_from_imfs,
}; };
@@ -67,12 +67,12 @@ impl_from!(BuildError {
}); });
fn xml_encode_config() -> rbx_xml::EncodeOptions { fn xml_encode_config() -> rbx_xml::EncodeOptions {
rbx_xml::EncodeOptions::new() rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
.property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
} }
pub fn build(options: &BuildOptions) -> Result<(), BuildError> { pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
let output_kind = options.output_kind let output_kind = options
.output_kind
.or_else(|| detect_output_kind(options)) .or_else(|| detect_output_kind(options))
.ok_or(BuildError::UnknownOutputKind)?; .ok_or(BuildError::UnknownOutputKind)?;
@@ -89,7 +89,8 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
let mut imfs = Imfs::new(RealFetcher::new(WatchMode::Disabled)); let mut imfs = Imfs::new(RealFetcher::new(WatchMode::Disabled));
log::trace!("Reading project root"); log::trace!("Reading project root");
let entry = imfs.get(&options.fuzzy_project_path) let entry = imfs
.get(&options.fuzzy_project_path)
.expect("could not get project path"); .expect("could not get project path");
log::trace!("Generating snapshot of instances from IMFS"); log::trace!("Generating snapshot of instances from IMFS");
@@ -112,17 +113,17 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
// descendants. // descendants.
rbx_xml::to_writer(&mut file, &tree, &[root_id], xml_encode_config())?; rbx_xml::to_writer(&mut file, &tree, &[root_id], xml_encode_config())?;
}, }
OutputKind::Rbxlx => { OutputKind::Rbxlx => {
// Place files don't contain an entry for the DataModel, but our // Place files don't contain an entry for the DataModel, but our
// RbxTree representation does. // RbxTree representation does.
let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids(); let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids();
rbx_xml::to_writer(&mut file, &tree, top_level_ids, xml_encode_config())?; rbx_xml::to_writer(&mut file, &tree, top_level_ids, xml_encode_config())?;
}, }
OutputKind::Rbxm => { OutputKind::Rbxm => {
rbx_binary::encode(&tree, &[root_id], &mut file)?; rbx_binary::encode(&tree, &[root_id], &mut file)?;
}, }
OutputKind::Rbxl => { OutputKind::Rbxl => {
log::warn!("Support for building binary places (rbxl) is still experimental."); log::warn!("Support for building binary places (rbxl) is still experimental.");
log::warn!("Using the XML place format (rbxlx) is recommended instead."); log::warn!("Using the XML place format (rbxlx) is recommended instead.");
@@ -130,7 +131,7 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> {
let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids(); let top_level_ids = tree.get_instance(root_id).unwrap().get_children_ids();
rbx_binary::encode(&tree, top_level_ids, &mut file)?; rbx_binary::encode(&tree, top_level_ids, &mut file)?;
}, }
} }
file.flush()?; file.flush()?;

View File

@@ -1,6 +1,4 @@
use std::{ use std::path::PathBuf;
path::PathBuf,
};
use failure::Fail; use failure::Fail;
@@ -8,11 +6,14 @@ use crate::project::{Project, ProjectInitError};
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
pub enum InitError { pub enum InitError {
#[fail(display = "Invalid project kind '{}', valid kinds are 'place' and 'model'", _0)] #[fail(
display = "Invalid project kind '{}', valid kinds are 'place' and 'model'",
_0
)]
InvalidKind(String), InvalidKind(String),
#[fail(display = "Project init error: {}", _0)] #[fail(display = "Project init error: {}", _0)]
ProjectInitError(#[fail(cause)] ProjectInitError) ProjectInitError(#[fail(cause)] ProjectInitError),
} }
impl_from!(InitError { impl_from!(InitError {
@@ -30,15 +31,19 @@ pub fn init(options: &InitOptions) -> Result<(), InitError> {
Some("place") | None => { Some("place") | None => {
let path = Project::init_place(&options.fuzzy_project_path)?; let path = Project::init_place(&options.fuzzy_project_path)?;
(path, "place") (path, "place")
}, }
Some("model") => { Some("model") => {
let path = Project::init_model(&options.fuzzy_project_path)?; let path = Project::init_model(&options.fuzzy_project_path)?;
(path, "model") (path, "model")
}, }
Some(invalid) => return Err(InitError::InvalidKind(invalid.to_string())), Some(invalid) => return Err(InitError::InvalidKind(invalid.to_string())),
}; };
println!("Created new {} project file at {}", project_kind, project_path.display()); println!(
"Created new {} project file at {}",
project_kind,
project_path.display()
);
Ok(()) Ok(())
} }

View File

@@ -1,9 +1,9 @@
mod serve;
mod init;
mod build; mod build;
mod init;
mod serve;
mod upload; mod upload;
pub use self::serve::*;
pub use self::init::*;
pub use self::build::*; pub use self::build::*;
pub use self::init::*;
pub use self::serve::*;
pub use self::upload::*; pub use self::upload::*;

View File

@@ -1,11 +1,7 @@
use std::{ use std::{collections::HashMap, path::PathBuf, sync::Arc};
collections::HashMap,
path::PathBuf,
sync::Arc,
};
use rbx_dom_weak::{RbxTree, RbxInstanceProperties};
use failure::Fail; use failure::Fail;
use rbx_dom_weak::{RbxInstanceProperties, RbxTree};
use crate::{ use crate::{
imfs::new::{Imfs, RealFetcher, WatchMode}, imfs::new::{Imfs, RealFetcher, WatchMode},
@@ -41,8 +37,11 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
Err(other) => return Err(other.into()), Err(other) => return Err(other.into()),
}; };
let port = options.port let port = options
.or(maybe_project.as_ref().and_then(|project| project.serve_port)) .port
.or(maybe_project
.as_ref()
.and_then(|project| project.serve_port))
.unwrap_or(DEFAULT_PORT); .unwrap_or(DEFAULT_PORT);
println!("Rojo server listening on port {}", port); println!("Rojo server listening on port {}", port);
@@ -55,7 +54,8 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
let root_id = tree.get_root_id(); let root_id = tree.get_root_id();
let mut imfs = Imfs::new(RealFetcher::new(WatchMode::Enabled)); let mut imfs = Imfs::new(RealFetcher::new(WatchMode::Enabled));
let entry = imfs.get(&options.fuzzy_project_path) let entry = imfs
.get(&options.fuzzy_project_path)
.expect("could not get project path"); .expect("could not get project path");
let snapshot = snapshot_from_imfs(&mut imfs, &entry) let snapshot = snapshot_from_imfs(&mut imfs, &entry)

View File

@@ -1,8 +1,4 @@
use std::{ use std::{fmt, io, path::PathBuf};
io,
fmt,
path::PathBuf,
};
use failure::Fail; use failure::Fail;

View File

@@ -8,9 +8,9 @@ use crossbeam_channel::Receiver;
use crate::path_map::PathMap; use crate::path_map::PathMap;
use super::{ use super::{
error::{FsError, FsResult},
fetcher::{FileType, ImfsEvent, ImfsFetcher},
snapshot::ImfsSnapshot, snapshot::ImfsSnapshot,
error::{FsResult, FsError},
fetcher::{ImfsFetcher, FileType, ImfsEvent},
}; };
/// An in-memory filesystem that can be incrementally populated and updated as /// An in-memory filesystem that can be incrementally populated and updated as
@@ -89,16 +89,22 @@ impl<F: ImfsFetcher> Imfs<F> {
match snapshot { match snapshot {
ImfsSnapshot::File(file) => { ImfsSnapshot::File(file) => {
self.inner.insert(path.to_path_buf(), ImfsItem::File(ImfsFile { self.inner.insert(
path: path.to_path_buf(), path.to_path_buf(),
contents: Some(file.contents), ImfsItem::File(ImfsFile {
})); path: path.to_path_buf(),
contents: Some(file.contents),
}),
);
} }
ImfsSnapshot::Directory(directory) => { ImfsSnapshot::Directory(directory) => {
self.inner.insert(path.to_path_buf(), ImfsItem::Directory(ImfsDirectory { self.inner.insert(
path: path.to_path_buf(), path.to_path_buf(),
children_enumerated: true, ImfsItem::Directory(ImfsDirectory {
})); path: path.to_path_buf(),
children_enumerated: true,
}),
);
for (child_name, child) in directory.children.into_iter() { for (child_name, child) in directory.children.into_iter() {
self.load_from_snapshot(path.join(child_name), child); self.load_from_snapshot(path.join(child_name), child);
@@ -114,7 +120,9 @@ impl<F: ImfsFetcher> Imfs<F> {
return Ok(()); return Ok(());
} }
let new_type = self.fetcher.file_type(path) let new_type = self
.fetcher
.file_type(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?; .map_err(|err| FsError::new(err, path.to_path_buf()))?;
match self.inner.get_mut(path) { match self.inner.get_mut(path) {
@@ -131,18 +139,25 @@ impl<F: ImfsFetcher> Imfs<F> {
} }
(ImfsItem::File(_), FileType::Directory) => { (ImfsItem::File(_), FileType::Directory) => {
self.inner.remove(path); self.inner.remove(path);
self.inner.insert(path.to_path_buf(), ImfsItem::new_from_type(FileType::Directory, path)); self.inner.insert(
path.to_path_buf(),
ImfsItem::new_from_type(FileType::Directory, path),
);
self.fetcher.watch(path); self.fetcher.watch(path);
} }
(ImfsItem::Directory(_), FileType::File) => { (ImfsItem::Directory(_), FileType::File) => {
self.inner.remove(path); self.inner.remove(path);
self.inner.insert(path.to_path_buf(), ImfsItem::new_from_type(FileType::File, path)); self.inner.insert(
path.to_path_buf(),
ImfsItem::new_from_type(FileType::File, path),
);
self.fetcher.unwatch(path); self.fetcher.unwatch(path);
} }
} }
} }
None => { None => {
self.inner.insert(path.to_path_buf(), ImfsItem::new_from_type(new_type, path)); self.inner
.insert(path.to_path_buf(), ImfsItem::new_from_type(new_type, path));
} }
} }
@@ -185,13 +200,19 @@ impl<F: ImfsFetcher> Imfs<F> {
match self.inner.get_mut(path).unwrap() { match self.inner.get_mut(path).unwrap() {
ImfsItem::File(file) => { ImfsItem::File(file) => {
if file.contents.is_none() { if file.contents.is_none() {
file.contents = Some(self.fetcher.read_contents(path) file.contents = Some(
.map_err(|err| FsError::new(err, path.to_path_buf()))?); self.fetcher
.read_contents(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?,
);
} }
Ok(file.contents.as_ref().unwrap()) Ok(file.contents.as_ref().unwrap())
} }
ImfsItem::Directory(_) => Err(FsError::new(io::Error::new(io::ErrorKind::Other, "Can't read a directory"), path.to_path_buf())) ImfsItem::Directory(_) => Err(FsError::new(
io::Error::new(io::ErrorKind::Other, "Can't read a directory"),
path.to_path_buf(),
)),
} }
} }
@@ -205,7 +226,9 @@ impl<F: ImfsFetcher> Imfs<F> {
self.fetcher.watch(path); self.fetcher.watch(path);
if dir.children_enumerated { if dir.children_enumerated {
return self.inner.children(path) return self
.inner
.children(path)
.unwrap() // TODO: Handle None here, which means the PathMap entry did not exist. .unwrap() // TODO: Handle None here, which means the PathMap entry did not exist.
.into_iter() .into_iter()
.map(PathBuf::from) // Convert paths from &Path to PathBuf .map(PathBuf::from) // Convert paths from &Path to PathBuf
@@ -215,13 +238,17 @@ impl<F: ImfsFetcher> Imfs<F> {
.collect::<FsResult<Vec<ImfsEntry>>>(); .collect::<FsResult<Vec<ImfsEntry>>>();
} }
self.fetcher.read_children(path) self.fetcher
.read_children(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))? .map_err(|err| FsError::new(err, path.to_path_buf()))?
.into_iter() .into_iter()
.map(|path| self.get(path)) .map(|path| self.get(path))
.collect::<FsResult<Vec<ImfsEntry>>>() .collect::<FsResult<Vec<ImfsEntry>>>()
} }
ImfsItem::File(_) => Err(FsError::new(io::Error::new(io::ErrorKind::Other, "Can't read a directory"), path.to_path_buf())) ImfsItem::File(_) => Err(FsError::new(
io::Error::new(io::ErrorKind::Other, "Can't read a directory"),
path.to_path_buf(),
)),
} }
} }
@@ -256,14 +283,17 @@ impl<F: ImfsFetcher> Imfs<F> {
/// is using, this call may read exactly only the given path and no more. /// is using, this call may read exactly only the given path and no more.
fn read_if_not_exists(&mut self, path: &Path) -> FsResult<()> { fn read_if_not_exists(&mut self, path: &Path) -> FsResult<()> {
if !self.inner.contains_key(path) { if !self.inner.contains_key(path) {
let kind = self.fetcher.file_type(path) let kind = self
.fetcher
.file_type(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?; .map_err(|err| FsError::new(err, path.to_path_buf()))?;
if kind == FileType::Directory { if kind == FileType::Directory {
self.fetcher.watch(path); self.fetcher.watch(path);
} }
self.inner.insert(path.to_path_buf(), ImfsItem::new_from_type(kind, path)); self.inner
.insert(path.to_path_buf(), ImfsItem::new_from_type(kind, path));
} }
Ok(()) Ok(())
@@ -293,10 +323,7 @@ impl ImfsEntry {
imfs.get_contents(&self.path) imfs.get_contents(&self.path)
} }
pub fn children( pub fn children(&self, imfs: &mut Imfs<impl ImfsFetcher>) -> FsResult<Vec<ImfsEntry>> {
&self,
imfs: &mut Imfs<impl ImfsFetcher>,
) -> FsResult<Vec<ImfsEntry>> {
imfs.get_children(&self.path) imfs.get_children(&self.path)
} }
@@ -352,19 +379,12 @@ pub struct ImfsDirectory {
mod test { mod test {
use super::*; use super::*;
use std::{ use std::{cell::RefCell, rc::Rc};
rc::Rc,
cell::RefCell,
};
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use maplit::hashmap; use maplit::hashmap;
use super::super::{ use super::super::{error::FsErrorKind, fetcher::ImfsEvent, noop_fetcher::NoopFetcher};
noop_fetcher::NoopFetcher,
error::FsErrorKind,
fetcher::ImfsEvent,
};
#[test] #[test]
fn from_snapshot_file() { fn from_snapshot_file() {
@@ -458,11 +478,9 @@ mod test {
unimplemented!(); unimplemented!();
} }
fn watch(&mut self, _path: &Path) { fn watch(&mut self, _path: &Path) {}
}
fn unwatch(&mut self, _path: &Path) { fn unwatch(&mut self, _path: &Path) {}
}
fn receiver(&self) -> Receiver<ImfsEvent> { fn receiver(&self) -> Receiver<ImfsEvent> {
crossbeam_channel::never() crossbeam_channel::never()
@@ -477,11 +495,9 @@ mod test {
inner: mock_state.clone(), inner: mock_state.clone(),
}); });
let a = imfs.get("/dir/a.txt") let a = imfs.get("/dir/a.txt").expect("mock file did not exist");
.expect("mock file did not exist");
let contents = a.contents(&mut imfs) let contents = a.contents(&mut imfs).expect("mock file contents error");
.expect("mock file contents error");
assert_eq!(contents, b"Initial contents"); assert_eq!(contents, b"Initial contents");
@@ -493,8 +509,7 @@ mod test {
imfs.raise_file_changed("/dir/a.txt") imfs.raise_file_changed("/dir/a.txt")
.expect("error processing file change"); .expect("error processing file change");
let contents = a.contents(&mut imfs) let contents = a.contents(&mut imfs).expect("mock file contents error");
.expect("mock file contents error");
assert_eq!(contents, b"Changed contents"); assert_eq!(contents, b"Changed contents");
} }
@@ -506,10 +521,10 @@ mod test {
let file = ImfsSnapshot::file("hello, world!"); let file = ImfsSnapshot::file("hello, world!");
imfs.load_from_snapshot("/hello.txt", file); imfs.load_from_snapshot("/hello.txt", file);
let hello = imfs.get("/hello.txt") let hello = imfs.get("/hello.txt").expect("couldn't get hello.txt");
.expect("couldn't get hello.txt");
let contents = hello.contents(&mut imfs) let contents = hello
.contents(&mut imfs)
.expect("couldn't get hello.txt contents"); .expect("couldn't get hello.txt contents");
assert_eq!(contents, b"hello, world!"); assert_eq!(contents, b"hello, world!");

View File

@@ -9,9 +9,9 @@ pub use error::*;
pub mod new { pub mod new {
pub use super::error::*; pub use super::error::*;
pub use super::imfs::*;
pub use super::fetcher::*; pub use super::fetcher::*;
pub use super::real_fetcher::*; pub use super::imfs::*;
pub use super::noop_fetcher::*; pub use super::noop_fetcher::*;
pub use super::real_fetcher::*;
pub use super::snapshot::*; pub use super::snapshot::*;
} }

View File

@@ -8,21 +8,30 @@ use std::{
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use super::fetcher::{ImfsFetcher, FileType, ImfsEvent}; use super::fetcher::{FileType, ImfsEvent, ImfsFetcher};
pub struct NoopFetcher; pub struct NoopFetcher;
impl ImfsFetcher for NoopFetcher { impl ImfsFetcher for NoopFetcher {
fn file_type(&mut self, _path: &Path) -> io::Result<FileType> { fn file_type(&mut self, _path: &Path) -> io::Result<FileType> {
Err(io::Error::new(io::ErrorKind::NotFound, "NoopFetcher always returns NotFound")) Err(io::Error::new(
io::ErrorKind::NotFound,
"NoopFetcher always returns NotFound",
))
} }
fn read_children(&mut self, _path: &Path) -> io::Result<Vec<PathBuf>> { fn read_children(&mut self, _path: &Path) -> io::Result<Vec<PathBuf>> {
Err(io::Error::new(io::ErrorKind::NotFound, "NoopFetcher always returns NotFound")) Err(io::Error::new(
io::ErrorKind::NotFound,
"NoopFetcher always returns NotFound",
))
} }
fn read_contents(&mut self, _path: &Path) -> io::Result<Vec<u8>> { fn read_contents(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
Err(io::Error::new(io::ErrorKind::NotFound, "NoopFetcher always returns NotFound")) Err(io::Error::new(
io::ErrorKind::NotFound,
"NoopFetcher always returns NotFound",
))
} }
fn create_directory(&mut self, _path: &Path) -> io::Result<()> { fn create_directory(&mut self, _path: &Path) -> io::Result<()> {
@@ -37,11 +46,9 @@ impl ImfsFetcher for NoopFetcher {
Ok(()) Ok(())
} }
fn watch(&mut self, _path: &Path) { fn watch(&mut self, _path: &Path) {}
}
fn unwatch(&mut self, _path: &Path) { fn unwatch(&mut self, _path: &Path) {}
}
fn receiver(&self) -> Receiver<ImfsEvent> { fn receiver(&self) -> Receiver<ImfsEvent> {
crossbeam_channel::never() crossbeam_channel::never()

View File

@@ -2,18 +2,17 @@
//! std::fs interface and notify as the file watcher. //! std::fs interface and notify as the file watcher.
use std::{ use std::{
fs, fs, io,
io,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::mpsc, sync::mpsc,
time::Duration, time::Duration,
}; };
use crossbeam_channel::{unbounded, Receiver};
use jod_thread::JoinHandle; use jod_thread::JoinHandle;
use crossbeam_channel::{Receiver, unbounded}; use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use notify::{RecursiveMode, RecommendedWatcher, Watcher};
use super::fetcher::{ImfsFetcher, FileType, ImfsEvent}; use super::fetcher::{FileType, ImfsEvent, ImfsFetcher};
/// Workaround to disable the file watcher for processes that don't need it, /// Workaround to disable the file watcher for processes that don't need it,
/// since notify appears hang on to mpsc Sender objects too long, causing Rojo /// since notify appears hang on to mpsc Sender objects too long, causing Rojo
@@ -51,7 +50,7 @@ impl RealFetcher {
.spawn(move || { .spawn(move || {
notify_receiver notify_receiver
.into_iter() .into_iter()
.for_each(|event| { sender.send(event).unwrap() }); .for_each(|event| sender.send(event).unwrap());
}) })
.expect("Could not start message converter thread"); .expect("Could not start message converter thread");
@@ -59,10 +58,10 @@ impl RealFetcher {
// causing our program to deadlock. Once this is fixed, watcher no // causing our program to deadlock. Once this is fixed, watcher no
// longer needs to be optional, but is still maybe useful? // longer needs to be optional, but is still maybe useful?
let watcher = match watch_mode { let watcher = match watch_mode {
WatchMode::Enabled => { WatchMode::Enabled => Some(
Some(notify::watcher(notify_sender, Duration::from_millis(300)) notify::watcher(notify_sender, Duration::from_millis(300))
.expect("Couldn't start 'notify' file watcher")) .expect("Couldn't start 'notify' file watcher"),
} ),
WatchMode::Disabled => None, WatchMode::Disabled => None,
}; };

View File

@@ -16,14 +16,9 @@ impl ImfsSnapshot {
/// Create a new directory ImfsSnapshot with the given children. /// Create a new directory ImfsSnapshot with the given children.
pub fn dir<S: Into<String>>(children: HashMap<S, ImfsSnapshot>) -> ImfsSnapshot { pub fn dir<S: Into<String>>(children: HashMap<S, ImfsSnapshot>) -> ImfsSnapshot {
let children = children let children = children.into_iter().map(|(k, v)| (k.into(), v)).collect();
.into_iter()
.map(|(k, v)| (k.into(), v))
.collect();
ImfsSnapshot::Directory(DirectorySnapshot { ImfsSnapshot::Directory(DirectorySnapshot { children })
children,
})
} }
} }

View File

@@ -1,4 +1,4 @@
#![recursion_limit="128"] #![recursion_limit = "128"]
// Macros // Macros
#[macro_use] #[macro_use]

View File

@@ -1,9 +1,6 @@
use std::{ use std::{
mem, mem,
sync::{ sync::{Mutex, RwLock},
RwLock,
Mutex,
},
}; };
use futures::sync::oneshot; use futures::sync::oneshot;
@@ -13,7 +10,10 @@ struct Listener<T> {
cursor: u32, cursor: u32,
} }
fn fire_listener_if_ready<T: Clone>(messages: &[T], listener: Listener<T>) -> Result<(), Listener<T>> { fn fire_listener_if_ready<T: Clone>(
messages: &[T],
listener: Listener<T>,
) -> Result<(), Listener<T>> {
let current_cursor = messages.len() as u32; let current_cursor = messages.len() as u32;
if listener.cursor < current_cursor { if listener.cursor < current_cursor {
@@ -30,8 +30,8 @@ fn fire_listener_if_ready<T: Clone>(messages: &[T], listener: Listener<T>) -> Re
/// Definitely non-optimal. This would ideally be a lockless mpmc queue. /// Definitely non-optimal. This would ideally be a lockless mpmc queue.
#[derive(Default)] #[derive(Default)]
pub struct MessageQueue<T> { pub struct MessageQueue<T> {
messages: RwLock<Vec<T>>, messages: RwLock<Vec<T>>,
message_listeners: Mutex<Vec<Listener<T>>>, message_listeners: Mutex<Vec<Listener<T>>>,
} }
impl<T: Clone> MessageQueue<T> { impl<T: Clone> MessageQueue<T> {
@@ -52,7 +52,7 @@ impl<T: Clone> MessageQueue<T> {
for listener in message_listeners.drain(..) { for listener in message_listeners.drain(..) {
match fire_listener_if_ready(&messages, listener) { match fire_listener_if_ready(&messages, listener) {
Ok(_) => {} Ok(_) => {}
Err(listener) => remaining_listeners.push(listener) Err(listener) => remaining_listeners.push(listener),
} }
} }
@@ -63,16 +63,13 @@ impl<T: Clone> MessageQueue<T> {
pub fn subscribe(&self, cursor: u32, sender: oneshot::Sender<(u32, Vec<T>)>) { pub fn subscribe(&self, cursor: u32, sender: oneshot::Sender<(u32, Vec<T>)>) {
let listener = { let listener = {
let listener = Listener { let listener = Listener { sender, cursor };
sender,
cursor,
};
let messages = self.messages.read().unwrap(); let messages = self.messages.read().unwrap();
match fire_listener_if_ready(&messages, listener) { match fire_listener_if_ready(&messages, listener) {
Ok(_) => return, Ok(_) => return,
Err(listener) => listener Err(listener) => listener,
} }
}; };

View File

@@ -1,10 +1,10 @@
use std::{ use std::{
path::{self, Path, PathBuf},
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
path::{self, Path, PathBuf},
}; };
use serde::Serialize;
use log::warn; use log::warn;
use serde::Serialize;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct PathMapNode<T> { struct PathMapNode<T> {
@@ -51,7 +51,9 @@ impl<T> PathMap<T> {
} }
pub fn children(&self, path: impl AsRef<Path>) -> Option<Vec<&Path>> { pub fn children(&self, path: impl AsRef<Path>) -> Option<Vec<&Path>> {
self.nodes.get(path.as_ref()).map(|v| v.children.iter().map(AsRef::as_ref).collect()) self.nodes
.get(path.as_ref())
.map(|v| v.children.iter().map(AsRef::as_ref).collect())
} }
pub fn contains_key(&self, path: impl AsRef<Path>) -> bool { pub fn contains_key(&self, path: impl AsRef<Path>) -> bool {
@@ -76,10 +78,7 @@ impl<T> PathMap<T> {
self.orphan_paths.remove(child); self.orphan_paths.remove(child);
} }
self.nodes.insert(path, PathMapNode { self.nodes.insert(path, PathMapNode { value, children });
value,
children,
});
} }
/// Remove the given path and all of its linked descendants, returning all /// Remove the given path and all of its linked descendants, returning all
@@ -105,10 +104,13 @@ impl<T> PathMap<T> {
for child in node.children.into_iter() { for child in node.children.into_iter() {
to_visit.push(child); to_visit.push(child);
} }
}, }
None => { None => {
warn!("Consistency issue; tried to remove {} but it was already removed", path.display()); warn!(
}, "Consistency issue; tried to remove {} but it was already removed",
path.display()
);
}
} }
} }
@@ -123,11 +125,16 @@ impl<T> PathMap<T> {
/// FS events, a file remove event could be followed by that file's /// FS events, a file remove event could be followed by that file's
/// directory being removed, in which case we should process that /// directory being removed, in which case we should process that
/// directory's parent. /// directory's parent.
pub fn descend(&self, start_path: impl Into<PathBuf>, target_path: impl AsRef<Path>) -> PathBuf { pub fn descend(
&self,
start_path: impl Into<PathBuf>,
target_path: impl AsRef<Path>,
) -> PathBuf {
let start_path = start_path.into(); let start_path = start_path.into();
let target_path = target_path.as_ref(); let target_path = target_path.as_ref();
let relative_path = target_path.strip_prefix(&start_path) let relative_path = target_path
.strip_prefix(&start_path)
.expect("target_path did not begin with start_path"); .expect("target_path did not begin with start_path");
let mut current_path = start_path; let mut current_path = start_path;
@@ -141,7 +148,7 @@ impl<T> PathMap<T> {
} else { } else {
return current_path; return current_path;
} }
}, }
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -219,9 +226,7 @@ mod test {
map.insert("/foo", 6); map.insert("/foo", 6);
assert_eq!(map.remove("/foo"), vec![ assert_eq!(map.remove("/foo"), vec![(PathBuf::from("/foo"), 6),]);
(PathBuf::from("/foo"), 6),
]);
assert_eq!(map.get("/foo"), None); assert_eq!(map.get("/foo"), None);
} }
@@ -233,10 +238,10 @@ mod test {
map.insert("/foo", 6); map.insert("/foo", 6);
map.insert("/foo/bar", 12); map.insert("/foo/bar", 12);
assert_eq!(map.remove("/foo"), vec![ assert_eq!(
(PathBuf::from("/foo"), 6), map.remove("/foo"),
(PathBuf::from("/foo/bar"), 12), vec![(PathBuf::from("/foo"), 6), (PathBuf::from("/foo/bar"), 12),]
]); );
assert_eq!(map.get("/foo"), None); assert_eq!(map.get("/foo"), None);
assert_eq!(map.get("/foo/bar"), None); assert_eq!(map.get("/foo/bar"), None);
@@ -250,11 +255,14 @@ mod test {
map.insert("/foo/bar", 12); map.insert("/foo/bar", 12);
map.insert("/foo/bar/baz", 18); map.insert("/foo/bar/baz", 18);
assert_eq!(map.remove("/foo"), vec![ assert_eq!(
(PathBuf::from("/foo"), 6), map.remove("/foo"),
(PathBuf::from("/foo/bar"), 12), vec![
(PathBuf::from("/foo/bar/baz"), 18), (PathBuf::from("/foo"), 6),
]); (PathBuf::from("/foo/bar"), 12),
(PathBuf::from("/foo/bar/baz"), 18),
]
);
assert_eq!(map.get("/foo"), None); assert_eq!(map.get("/foo"), None);
assert_eq!(map.get("/foo/bar"), None); assert_eq!(map.get("/foo/bar"), None);
@@ -268,9 +276,7 @@ mod test {
map.insert("/foo", 6); map.insert("/foo", 6);
map.insert("/foo/bar/baz", 12); map.insert("/foo/bar/baz", 12);
assert_eq!(map.remove("/foo"), vec![ assert_eq!(map.remove("/foo"), vec![(PathBuf::from("/foo"), 6),]);
(PathBuf::from("/foo"), 6),
]);
assert_eq!(map.get("/foo"), None); assert_eq!(map.get("/foo"), None);
assert_eq!(map.get("/foo/bar/baz"), Some(&12)); assert_eq!(map.get("/foo/bar/baz"), Some(&12));

View File

@@ -33,22 +33,27 @@ use std::path::{Component, Path};
use serde::Serializer; use serde::Serializer;
pub fn serialize_option<S, T>(maybe_path: &Option<T>, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize_option<S, T>(maybe_path: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer, where
T: AsRef<Path>, S: Serializer,
T: AsRef<Path>,
{ {
match maybe_path { match maybe_path {
Some(path) => serialize(path, serializer), Some(path) => serialize(path, serializer),
None => serializer.serialize_none() None => serializer.serialize_none(),
} }
} }
pub fn serialize<S, T>(path: T, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize<S, T>(path: T, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer, where
T: AsRef<Path>, S: Serializer,
T: AsRef<Path>,
{ {
let path = path.as_ref(); let path = path.as_ref();
assert!(path.is_relative(), "path_serializer can only handle relative paths"); assert!(
path.is_relative(),
"path_serializer can only handle relative paths"
);
let mut output = String::new(); let mut output = String::new();

View File

@@ -1,15 +1,15 @@
use std::{ use std::{
collections::{HashMap, HashSet, BTreeMap}, collections::{BTreeMap, HashMap, HashSet},
fmt, fmt,
fs::{self, File}, fs::{self, File},
io, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use log::warn;
use failure::Fail; use failure::Fail;
use rbx_dom_weak::{UnresolvedRbxValue, RbxValue}; use log::warn;
use serde::{Serialize, Serializer, Deserialize}; use rbx_dom_weak::{RbxValue, UnresolvedRbxValue};
use serde::{Deserialize, Serialize, Serializer};
static DEFAULT_PLACE: &'static str = include_str!("../assets/place.project.json"); static DEFAULT_PLACE: &'static str = include_str!("../assets/place.project.json");
@@ -40,7 +40,8 @@ impl SourceProject {
/// Consumes the SourceProject and yields a Project, ready for prime-time. /// Consumes the SourceProject and yields a Project, ready for prime-time.
pub fn into_project(mut self, project_file_location: &Path) -> Project { pub fn into_project(mut self, project_file_location: &Path) -> Project {
let tree = self.tree.into_project_node(project_file_location); let tree = self.tree.into_project_node(project_file_location);
let plugins = self.plugins let plugins = self
.plugins
.drain(..) .drain(..)
.map(|source_plugin| source_plugin.into_plugin(project_file_location)) .map(|source_plugin| source_plugin.into_plugin(project_file_location))
.collect(); .collect();
@@ -76,43 +77,48 @@ impl SourceProject {
/// ///
/// This holds true for other values that might be ambiguous or just have more /// This holds true for other values that might be ambiguous or just have more
/// complicated representations like enums. /// complicated representations like enums.
fn serialize_unresolved_minimal<S>(unresolved: &UnresolvedRbxValue, serializer: S) -> Result<S::Ok, S::Error> fn serialize_unresolved_minimal<S>(
where S: Serializer unresolved: &UnresolvedRbxValue,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{ {
match unresolved { match unresolved {
UnresolvedRbxValue::Ambiguous(_) => unresolved.serialize(serializer), UnresolvedRbxValue::Ambiguous(_) => unresolved.serialize(serializer),
UnresolvedRbxValue::Concrete(concrete) => { UnresolvedRbxValue::Concrete(concrete) => match concrete {
match concrete { RbxValue::Bool { value } => value.serialize(serializer),
RbxValue::Bool { value } => value.serialize(serializer), RbxValue::CFrame { value } => value.serialize(serializer),
RbxValue::CFrame { value } => value.serialize(serializer), RbxValue::Color3 { value } => value.serialize(serializer),
RbxValue::Color3 { value } => value.serialize(serializer), RbxValue::Color3uint8 { value } => value.serialize(serializer),
RbxValue::Color3uint8 { value } => value.serialize(serializer), RbxValue::Content { value } => value.serialize(serializer),
RbxValue::Content { value } => value.serialize(serializer), RbxValue::Float32 { value } => value.serialize(serializer),
RbxValue::Float32 { value } => value.serialize(serializer), RbxValue::Int32 { value } => value.serialize(serializer),
RbxValue::Int32 { value } => value.serialize(serializer), RbxValue::String { value } => value.serialize(serializer),
RbxValue::String { value } => value.serialize(serializer), RbxValue::UDim { value } => value.serialize(serializer),
RbxValue::UDim { value } => value.serialize(serializer), RbxValue::UDim2 { value } => value.serialize(serializer),
RbxValue::UDim2 { value } => value.serialize(serializer), RbxValue::Vector2 { value } => value.serialize(serializer),
RbxValue::Vector2 { value } => value.serialize(serializer), RbxValue::Vector2int16 { value } => value.serialize(serializer),
RbxValue::Vector2int16 { value } => value.serialize(serializer), RbxValue::Vector3 { value } => value.serialize(serializer),
RbxValue::Vector3 { value } => value.serialize(serializer), RbxValue::Vector3int16 { value } => value.serialize(serializer),
RbxValue::Vector3int16 { value } => value.serialize(serializer), _ => concrete.serialize(serializer),
_ => concrete.serialize(serializer),
}
}, },
} }
} }
/// A wrapper around serialize_unresolved_minimal that handles the HashMap case. /// A wrapper around serialize_unresolved_minimal that handles the HashMap case.
fn serialize_unresolved_map<S>(value: &HashMap<String, UnresolvedRbxValue>, serializer: S) -> Result<S::Ok, S::Error> fn serialize_unresolved_map<S>(
where S: Serializer value: &HashMap<String, UnresolvedRbxValue>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{ {
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
#[derive(Serialize)] #[derive(Serialize)]
struct Minimal<'a>( struct Minimal<'a>(
#[serde(serialize_with = "serialize_unresolved_minimal")] #[serde(serialize_with = "serialize_unresolved_minimal")] &'a UnresolvedRbxValue,
&'a UnresolvedRbxValue
); );
let mut map = serializer.serialize_map(Some(value.len()))?; let mut map = serializer.serialize_map(Some(value.len()))?;
@@ -135,11 +141,14 @@ struct SourceProjectNode {
rename = "$properties", rename = "$properties",
default = "HashMap::new", default = "HashMap::new",
skip_serializing_if = "HashMap::is_empty", skip_serializing_if = "HashMap::is_empty",
serialize_with = "serialize_unresolved_map", serialize_with = "serialize_unresolved_map"
)] )]
properties: HashMap<String, UnresolvedRbxValue>, properties: HashMap<String, UnresolvedRbxValue>,
#[serde(rename = "$ignoreUnknownInstances", skip_serializing_if = "Option::is_none")] #[serde(
rename = "$ignoreUnknownInstances",
skip_serializing_if = "Option::is_none"
)]
ignore_unknown_instances: Option<bool>, ignore_unknown_instances: Option<bool>,
#[serde(rename = "$path", skip_serializing_if = "Option::is_none")] #[serde(rename = "$path", skip_serializing_if = "Option::is_none")]
@@ -152,8 +161,15 @@ struct SourceProjectNode {
impl SourceProjectNode { impl SourceProjectNode {
/// Consumes the SourceProjectNode and turns it into a ProjectNode. /// Consumes the SourceProjectNode and turns it into a ProjectNode.
pub fn into_project_node(self, project_file_location: &Path) -> ProjectNode { pub fn into_project_node(self, project_file_location: &Path) -> ProjectNode {
let children = self.children.iter() let children = self
.map(|(key, value)| (key.clone(), value.clone().into_project_node(project_file_location))) .children
.iter()
.map(|(key, value)| {
(
key.clone(),
value.clone().into_project_node(project_file_location),
)
})
.collect(); .collect();
// Make sure that paths are absolute, transforming them by adding the // Make sure that paths are absolute, transforming them by adding the
@@ -191,9 +207,7 @@ impl SourcePlugin {
project_folder_location.join(self.path) project_folder_location.join(self.path)
}; };
Plugin { Plugin { path }
path,
}
} }
} }
@@ -219,15 +233,16 @@ impl fmt::Display for ProjectLoadError {
use self::ProjectLoadError::*; use self::ProjectLoadError::*;
match self { match self {
NotFound => { NotFound => write!(formatter, "Project file not found"),
write!(formatter, "Project file not found")
}
Io { inner, path } => { Io { inner, path } => {
write!(formatter, "I/O error: {} in path {}", inner, path.display()) write!(formatter, "I/O error: {} in path {}", inner, path.display())
} }
Json { inner, path } => { Json { inner, path } => write!(
write!(formatter, "JSON error: {} in path {}", inner, path.display()) formatter,
} "JSON error: {} in path {}",
inner,
path.display()
),
} }
} }
} }
@@ -244,7 +259,9 @@ pub enum ProjectInitError {
impl fmt::Display for ProjectInitError { impl fmt::Display for ProjectInitError {
fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
ProjectInitError::AlreadyExists(path) => write!(output, "Path {} already exists", path.display()), ProjectInitError::AlreadyExists(path) => {
write!(output, "Path {} already exists", path.display())
}
ProjectInitError::IoError(inner) => write!(output, "IO error: {}", inner), ProjectInitError::IoError(inner) => write!(output, "IO error: {}", inner),
ProjectInitError::SaveError(inner) => write!(output, "{}", inner), ProjectInitError::SaveError(inner) => write!(output, "{}", inner),
ProjectInitError::JsonError(inner) => write!(output, "{}", inner), ProjectInitError::JsonError(inner) => write!(output, "{}", inner),
@@ -277,8 +294,13 @@ impl ProjectNode {
fn validate_reserved_names(&self) { fn validate_reserved_names(&self) {
for (name, child) in &self.children { for (name, child) in &self.children {
if name.starts_with('$') { if name.starts_with('$') {
warn!("Keys starting with '$' are reserved by Rojo to ensure forward compatibility."); warn!(
warn!("This project uses the key '{}', which should be renamed.", name); "Keys starting with '$' are reserved by Rojo to ensure forward compatibility."
);
warn!(
"This project uses the key '{}', which should be renamed.",
name
);
} }
child.validate_reserved_names(); child.validate_reserved_names();
@@ -286,7 +308,9 @@ impl ProjectNode {
} }
fn to_source_node(&self, project_file_location: &Path) -> SourceProjectNode { fn to_source_node(&self, project_file_location: &Path) -> SourceProjectNode {
let children = self.children.iter() let children = self
.children
.iter()
.map(|(key, value)| (key.clone(), value.to_source_node(project_file_location))) .map(|(key, value)| (key.clone(), value.to_source_node(project_file_location)))
.collect(); .collect();
@@ -329,9 +353,7 @@ impl Plugin {
Err(_) => format!("{}", self.path.display()), Err(_) => format!("{}", self.path.display()),
}; };
SourcePlugin { SourcePlugin { path }
path,
}
} }
} }
@@ -351,13 +373,18 @@ impl Project {
let project_name = if project_fuzzy_path == project_path { let project_name = if project_fuzzy_path == project_path {
project_fuzzy_path project_fuzzy_path
.parent().expect("Path did not have a parent directory") .parent()
.file_name().expect("Path did not have a file name") .expect("Path did not have a parent directory")
.to_str().expect("Path had invalid Unicode") .file_name()
.expect("Path did not have a file name")
.to_str()
.expect("Path had invalid Unicode")
} else { } else {
project_fuzzy_path project_fuzzy_path
.file_name().expect("Path did not have a file name") .file_name()
.to_str().expect("Path had invalid Unicode") .expect("Path did not have a file name")
.to_str()
.expect("Path had invalid Unicode")
}; };
let mut project = Project::load_from_str(DEFAULT_PLACE, &project_path) let mut project = Project::load_from_str(DEFAULT_PLACE, &project_path)
@@ -365,8 +392,7 @@ impl Project {
project.name = project_name.to_owned(); project.name = project_name.to_owned();
project.save() project.save().map_err(ProjectInitError::SaveError)?;
.map_err(ProjectInitError::SaveError)?;
Ok(project_path) Ok(project_path)
} }
@@ -376,17 +402,23 @@ impl Project {
let project_name = if project_fuzzy_path == project_path { let project_name = if project_fuzzy_path == project_path {
project_fuzzy_path project_fuzzy_path
.parent().expect("Path did not have a parent directory") .parent()
.file_name().expect("Path did not have a file name") .expect("Path did not have a parent directory")
.to_str().expect("Path had invalid Unicode") .file_name()
.expect("Path did not have a file name")
.to_str()
.expect("Path had invalid Unicode")
} else { } else {
project_fuzzy_path project_fuzzy_path
.file_name().expect("Path did not have a file name") .file_name()
.to_str().expect("Path had invalid Unicode") .expect("Path did not have a file name")
.to_str()
.expect("Path had invalid Unicode")
}; };
let project_folder_path = project_path let project_folder_path = project_path
.parent().expect("Path did not have a parent directory"); .parent()
.expect("Path did not have a parent directory");
let tree = ProjectNode { let tree = ProjectNode {
path: Some(project_folder_path.join("src")), path: Some(project_folder_path.join("src")),
@@ -402,8 +434,7 @@ impl Project {
file_location: project_path.clone(), file_location: project_path.clone(),
}; };
project.save() project.save().map_err(ProjectInitError::SaveError)?;
.map_err(ProjectInitError::SaveError)?;
Ok(project_path) Ok(project_path)
} }
@@ -419,7 +450,7 @@ impl Project {
match fs::metadata(&project_path) { match fs::metadata(&project_path) {
Err(error) => match error.kind() { Err(error) => match error.kind() {
io::ErrorKind::NotFound => {}, io::ErrorKind::NotFound => {}
_ => return Err(ProjectInitError::IoError(error)), _ => return Err(ProjectInitError::IoError(error)),
}, },
Ok(_) => return Err(ProjectInitError::AlreadyExists(project_path)), Ok(_) => return Err(ProjectInitError::AlreadyExists(project_path)),
@@ -459,13 +490,19 @@ impl Project {
} }
} }
fn load_from_str(contents: &str, project_file_location: &Path) -> Result<Project, serde_json::Error> { fn load_from_str(
contents: &str,
project_file_location: &Path,
) -> Result<Project, serde_json::Error> {
let parsed: SourceProject = serde_json::from_str(&contents)?; let parsed: SourceProject = serde_json::from_str(&contents)?;
Ok(parsed.into_project(project_file_location)) Ok(parsed.into_project(project_file_location))
} }
pub fn load_from_slice(contents: &[u8], project_file_location: &Path) -> Result<Project, serde_json::Error> { pub fn load_from_slice(
contents: &[u8],
project_file_location: &Path,
) -> Result<Project, serde_json::Error> {
let parsed: SourceProject = serde_json::from_slice(&contents)?; let parsed: SourceProject = serde_json::from_slice(&contents)?;
Ok(parsed.into_project(project_file_location)) Ok(parsed.into_project(project_file_location))
@@ -481,17 +518,17 @@ impl Project {
} }
pub fn load_exact(project_file_location: &Path) -> Result<Project, ProjectLoadError> { pub fn load_exact(project_file_location: &Path) -> Result<Project, ProjectLoadError> {
let contents = fs::read_to_string(project_file_location) let contents =
.map_err(|error| match error.kind() { fs::read_to_string(project_file_location).map_err(|error| match error.kind() {
io::ErrorKind::NotFound => ProjectLoadError::NotFound, io::ErrorKind::NotFound => ProjectLoadError::NotFound,
_ => ProjectLoadError::Io { _ => ProjectLoadError::Io {
inner: error, inner: error,
path: project_file_location.to_path_buf(), path: project_file_location.to_path_buf(),
} },
})?; })?;
let parsed: SourceProject = serde_json::from_str(&contents) let parsed: SourceProject =
.map_err(|error| ProjectLoadError::Json { serde_json::from_str(&contents).map_err(|error| ProjectLoadError::Json {
inner: error, inner: error,
path: project_file_location.to_path_buf(), path: project_file_location.to_path_buf(),
})?; })?;
@@ -504,8 +541,7 @@ impl Project {
pub fn save(&self) -> Result<(), ProjectSaveError> { pub fn save(&self) -> Result<(), ProjectSaveError> {
let source_project = self.to_source_project(); let source_project = self.to_source_project();
let mut file = File::create(&self.file_location) let mut file = File::create(&self.file_location).map_err(ProjectSaveError::IoError)?;
.map_err(ProjectSaveError::IoError)?;
serde_json::to_writer_pretty(&mut file, &source_project) serde_json::to_writer_pretty(&mut file, &source_project)
.map_err(ProjectSaveError::JsonError)?; .map_err(ProjectSaveError::JsonError)?;
@@ -516,9 +552,12 @@ impl Project {
/// Checks if there are any compatibility issues with this project file and /// Checks if there are any compatibility issues with this project file and
/// warns the user if there are any. /// warns the user if there are any.
fn check_compatibility(&self) { fn check_compatibility(&self) {
let file_name = self.file_location let file_name = self
.file_name().expect("Project file path did not have a file name") .file_location
.to_str().expect("Project file path was not valid Unicode"); .file_name()
.expect("Project file path did not have a file name")
.to_str()
.expect("Project file path was not valid Unicode");
if file_name == COMPAT_PROJECT_FILENAME { if file_name == COMPAT_PROJECT_FILENAME {
warn!("Rojo's default project file name changed in 0.5.0-alpha3."); warn!("Rojo's default project file name changed in 0.5.0-alpha3.");
@@ -554,7 +593,8 @@ impl Project {
} }
fn to_source_project(&self) -> SourceProject { fn to_source_project(&self) -> SourceProject {
let plugins = self.plugins let plugins = self
.plugins
.iter() .iter()
.map(|plugin| plugin.to_source_plugin(&self.file_location)) .map(|plugin| plugin.to_source_plugin(&self.file_location))
.collect(); .collect();

View File

@@ -1,9 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{project::Project, session_id::SessionId};
project::Project,
session_id::SessionId,
};
/// Contains all of the state for a Rojo serve session. /// Contains all of the state for a Rojo serve session.
pub struct ServeSession { pub struct ServeSession {

View File

@@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@@ -1,11 +1,8 @@
//! Defines the structure of an instance snapshot. //! Defines the structure of an instance snapshot.
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use rbx_dom_weak::{RbxTree, RbxId, RbxValue}; use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
/// A lightweight description of what an instance should look like. Attempts to /// A lightweight description of what an instance should look like. Attempts to
/// be somewhat memory efficient by borrowing from its source data, indicated by /// be somewhat memory efficient by borrowing from its source data, indicated by
@@ -22,13 +19,14 @@ pub struct InstanceSnapshot<'source> {
pub class_name: Cow<'source, str>, pub class_name: Cow<'source, str>,
pub properties: HashMap<String, RbxValue>, pub properties: HashMap<String, RbxValue>,
pub children: Vec<InstanceSnapshot<'source>>, pub children: Vec<InstanceSnapshot<'source>>,
// TODO: Snapshot source, like a file or a project node? // TODO: Snapshot source, like a file or a project node?
} }
impl<'source> InstanceSnapshot<'source> { impl<'source> InstanceSnapshot<'source> {
pub fn get_owned(&'source self) -> InstanceSnapshot<'static> { pub fn get_owned(&'source self) -> InstanceSnapshot<'static> {
let children: Vec<InstanceSnapshot<'static>> = self.children.iter() let children: Vec<InstanceSnapshot<'static>> = self
.children
.iter()
.map(InstanceSnapshot::get_owned) .map(InstanceSnapshot::get_owned)
.collect(); .collect();
@@ -42,10 +40,12 @@ impl<'source> InstanceSnapshot<'source> {
} }
pub fn from_tree(tree: &RbxTree, id: RbxId) -> InstanceSnapshot<'static> { pub fn from_tree(tree: &RbxTree, id: RbxId) -> InstanceSnapshot<'static> {
let instance = tree.get_instance(id) let instance = tree
.get_instance(id)
.expect("instance did not exist in tree"); .expect("instance did not exist in tree");
let children = instance.get_children_ids() let children = instance
.get_children_ids()
.iter() .iter()
.cloned() .cloned()
.map(|id| InstanceSnapshot::from_tree(tree, id)) .map(|id| InstanceSnapshot::from_tree(tree, id))

View File

@@ -18,12 +18,12 @@
#![allow(dead_code)] #![allow(dead_code)]
mod instance_snapshot;
mod patch; mod patch;
mod patch_apply; mod patch_apply;
mod patch_compute; mod patch_compute;
mod instance_snapshot;
pub use instance_snapshot::InstanceSnapshot; pub use instance_snapshot::InstanceSnapshot;
pub use patch::*;
pub use patch_apply::apply_patch_set; pub use patch_apply::apply_patch_set;
pub use patch_compute::compute_patch_set; pub use patch_compute::compute_patch_set;
pub use patch::*;

View File

@@ -2,7 +2,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use rbx_dom_weak::{RbxValue, RbxId}; use rbx_dom_weak::{RbxId, RbxValue};
use super::InstanceSnapshot; use super::InstanceSnapshot;

View File

@@ -2,17 +2,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use rbx_dom_weak::{RbxTree, RbxValue, RbxId, RbxInstanceProperties}; use rbx_dom_weak::{RbxId, RbxInstanceProperties, RbxTree, RbxValue};
use super::{ use super::{
patch::{PatchSet, PatchUpdateInstance}, patch::{PatchSet, PatchUpdateInstance},
InstanceSnapshot, InstanceSnapshot,
}; };
pub fn apply_patch_set( pub fn apply_patch_set(tree: &mut RbxTree, patch_set: &PatchSet) {
tree: &mut RbxTree,
patch_set: &PatchSet,
) {
let mut context = PatchApplyContext::default(); let mut context = PatchApplyContext::default();
for removed_id in &patch_set.removed_instances { for removed_id in &patch_set.removed_instances {
@@ -47,13 +44,16 @@ struct PatchApplyContext {
/// then apply properties all at once at the end. /// then apply properties all at once at the end.
fn apply_deferred_properties(context: PatchApplyContext, tree: &mut RbxTree) { fn apply_deferred_properties(context: PatchApplyContext, tree: &mut RbxTree) {
for (id, mut properties) in context.properties_to_apply { for (id, mut properties) in context.properties_to_apply {
let instance = tree.get_instance_mut(id) let instance = tree
.get_instance_mut(id)
.expect("Invalid instance ID in deferred property map"); .expect("Invalid instance ID in deferred property map");
for property_value in properties.values_mut() { for property_value in properties.values_mut() {
if let RbxValue::Ref { value: Some(id) } = property_value { if let RbxValue::Ref { value: Some(id) } = property_value {
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) { if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
*property_value = RbxValue::Ref { value: Some(instance_id) }; *property_value = RbxValue::Ref {
value: Some(instance_id),
};
} }
} }
} }
@@ -79,7 +79,9 @@ fn apply_add_child(
let id = tree.insert_instance(properties, parent_id); let id = tree.insert_instance(properties, parent_id);
context.properties_to_apply.insert(id, snapshot.properties.clone()); context
.properties_to_apply
.insert(id, snapshot.properties.clone());
if let Some(snapshot_id) = snapshot.snapshot_id { if let Some(snapshot_id) = snapshot.snapshot_id {
context.snapshot_id_to_instance_id.insert(snapshot_id, id); context.snapshot_id_to_instance_id.insert(snapshot_id, id);
@@ -95,7 +97,8 @@ fn apply_update_child(
tree: &mut RbxTree, tree: &mut RbxTree,
patch: &PatchUpdateInstance, patch: &PatchUpdateInstance,
) { ) {
let instance = tree.get_instance_mut(patch.id) let instance = tree
.get_instance_mut(patch.id)
.expect("Instance referred to by patch does not exist"); .expect("Instance referred to by patch does not exist");
if let Some(name) = &patch.changed_name { if let Some(name) = &patch.changed_name {
@@ -114,9 +117,12 @@ fn apply_update_child(
Some(RbxValue::Ref { value: Some(id) }) => { Some(RbxValue::Ref { value: Some(id) }) => {
let new_id = context.snapshot_id_to_instance_id.get(id).unwrap_or(id); let new_id = context.snapshot_id_to_instance_id.get(id).unwrap_or(id);
instance.properties.insert(key.clone(), RbxValue::Ref { instance.properties.insert(
value: Some(*new_id), key.clone(),
}); RbxValue::Ref {
value: Some(*new_id),
},
);
} }
Some(value) => { Some(value) => {
instance.properties.insert(key.clone(), value.clone()); instance.properties.insert(key.clone(), value.clone());
@@ -132,10 +138,7 @@ fn apply_update_child(
mod test { mod test {
use super::*; use super::*;
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::RbxValue; use rbx_dom_weak::RbxValue;
@@ -165,12 +168,10 @@ mod test {
}; };
let patch_set = PatchSet { let patch_set = PatchSet {
added_instances: vec![ added_instances: vec![PatchAddInstance {
PatchAddInstance { parent_id: root_id,
parent_id: root_id, instance: snapshot.clone(),
instance: snapshot.clone(), }],
}
],
..Default::default() ..Default::default()
}; };

View File

@@ -3,11 +3,11 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use rbx_dom_weak::{RbxTree, RbxValue, RbxId, RbxInstance}; use rbx_dom_weak::{RbxId, RbxInstance, RbxTree, RbxValue};
use super::{ use super::{
patch::{PatchAddInstance, PatchSet, PatchUpdateInstance},
InstanceSnapshot, InstanceSnapshot,
patch::{PatchSet, PatchAddInstance, PatchUpdateInstance},
}; };
pub fn compute_patch_set<'a>( pub fn compute_patch_set<'a>(
@@ -38,7 +38,9 @@ fn rewrite_refs_in_updates(context: &ComputePatchContext, updates: &mut [PatchUp
for property_value in update.changed_properties.values_mut() { for property_value in update.changed_properties.values_mut() {
if let Some(RbxValue::Ref { value: Some(id) }) = property_value { if let Some(RbxValue::Ref { value: Some(id) }) = property_value {
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) { if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
*property_value = Some(RbxValue::Ref { value: Some(instance_id) }); *property_value = Some(RbxValue::Ref {
value: Some(instance_id),
});
} }
} }
} }
@@ -55,7 +57,9 @@ fn rewrite_refs_in_snapshot(context: &ComputePatchContext, snapshot: &mut Instan
for property_value in snapshot.properties.values_mut() { for property_value in snapshot.properties.values_mut() {
if let RbxValue::Ref { value: Some(id) } = property_value { if let RbxValue::Ref { value: Some(id) } = property_value {
if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) { if let Some(&instance_id) = context.snapshot_id_to_instance_id.get(id) {
*property_value = RbxValue::Ref { value: Some(instance_id) }; *property_value = RbxValue::Ref {
value: Some(instance_id),
};
} }
} }
} }
@@ -76,7 +80,8 @@ fn compute_patch_set_internal<'a>(
context.snapshot_id_to_instance_id.insert(snapshot_id, id); context.snapshot_id_to_instance_id.insert(snapshot_id, id);
} }
let instance = tree.get_instance(id) let instance = tree
.get_instance(id)
.expect("Instance did not exist in tree"); .expect("Instance did not exist in tree");
compute_property_patches(snapshot, instance, patch_set); compute_property_patches(snapshot, instance, patch_set);
@@ -145,7 +150,8 @@ fn compute_children_patches<'a>(
id: RbxId, id: RbxId,
patch_set: &mut PatchSet<'a>, patch_set: &mut PatchSet<'a>,
) { ) {
let instance = tree.get_instance(id) let instance = tree
.get_instance(id)
.expect("Instance did not exist in tree"); .expect("Instance did not exist in tree");
let instance_children = instance.get_children_ids(); let instance_children = instance.get_children_ids();
@@ -153,30 +159,38 @@ fn compute_children_patches<'a>(
let mut paired_instances = vec![false; instance_children.len()]; let mut paired_instances = vec![false; instance_children.len()];
for snapshot_child in snapshot.children.iter() { for snapshot_child in snapshot.children.iter() {
let matching_instance = instance_children let matching_instance =
.iter() instance_children
.enumerate() .iter()
.find(|(instance_index, instance_child_id)| { .enumerate()
if paired_instances[*instance_index] { .find(|(instance_index, instance_child_id)| {
return false; if paired_instances[*instance_index] {
} return false;
}
let instance_child = tree.get_instance(**instance_child_id) let instance_child = tree
.expect("Instance did not exist in tree"); .get_instance(**instance_child_id)
.expect("Instance did not exist in tree");
if snapshot_child.name == instance_child.name && if snapshot_child.name == instance_child.name
instance_child.class_name == instance_child.class_name && instance_child.class_name == instance_child.class_name
{ {
paired_instances[*instance_index] = true; paired_instances[*instance_index] = true;
return true; return true;
} }
false false
}); });
match matching_instance { match matching_instance {
Some((_, instance_child_id)) => { Some((_, instance_child_id)) => {
compute_patch_set_internal(context, snapshot_child, tree, *instance_child_id, patch_set); compute_patch_set_internal(
context,
snapshot_child,
tree,
*instance_child_id,
patch_set,
);
} }
None => { None => {
patch_set.added_instances.push(PatchAddInstance { patch_set.added_instances.push(PatchAddInstance {
@@ -238,18 +252,16 @@ mod test {
let patch_set = compute_patch_set(&snapshot, &tree, root_id); let patch_set = compute_patch_set(&snapshot, &tree, root_id);
let expected_patch_set = PatchSet { let expected_patch_set = PatchSet {
updated_instances: vec![ updated_instances: vec![PatchUpdateInstance {
PatchUpdateInstance { id: root_id,
id: root_id, changed_name: None,
changed_name: None, changed_class_name: None,
changed_class_name: None, changed_properties: hashmap! {
changed_properties: hashmap! { "Self".to_owned() => Some(RbxValue::Ref {
"Self".to_owned() => Some(RbxValue::Ref { value: Some(root_id),
value: Some(root_id), }),
}),
},
}, },
], }],
added_instances: Vec::new(), added_instances: Vec::new(),
removed_instances: Vec::new(), removed_instances: Vec::new(),
}; };
@@ -274,20 +286,18 @@ mod test {
let snapshot_id = RbxId::new(); let snapshot_id = RbxId::new();
let snapshot = InstanceSnapshot { let snapshot = InstanceSnapshot {
snapshot_id: Some(snapshot_id), snapshot_id: Some(snapshot_id),
children: vec![ children: vec![InstanceSnapshot {
InstanceSnapshot { properties: hashmap! {
properties: hashmap! { "Self".to_owned() => RbxValue::Ref {
"Self".to_owned() => RbxValue::Ref { value: Some(snapshot_id),
value: Some(snapshot_id),
},
}, },
},
snapshot_id: None, snapshot_id: None,
name: Cow::Borrowed("child"), name: Cow::Borrowed("child"),
class_name: Cow::Borrowed("child"), class_name: Cow::Borrowed("child"),
children: Vec::new(), children: Vec::new(),
} }],
],
properties: HashMap::new(), properties: HashMap::new(),
name: Cow::Borrowed("foo"), name: Cow::Borrowed("foo"),
@@ -297,22 +307,20 @@ mod test {
let patch_set = compute_patch_set(&snapshot, &tree, root_id); let patch_set = compute_patch_set(&snapshot, &tree, root_id);
let expected_patch_set = PatchSet { let expected_patch_set = PatchSet {
added_instances: vec![ added_instances: vec![PatchAddInstance {
PatchAddInstance { parent_id: root_id,
parent_id: root_id, instance: InstanceSnapshot {
instance: InstanceSnapshot { snapshot_id: None,
snapshot_id: None, properties: hashmap! {
properties: hashmap! { "Self".to_owned() => RbxValue::Ref {
"Self".to_owned() => RbxValue::Ref { value: Some(root_id),
value: Some(root_id),
},
}, },
name: Cow::Borrowed("child"),
class_name: Cow::Borrowed("child"),
children: Vec::new(),
}, },
name: Cow::Borrowed("child"),
class_name: Cow::Borrowed("child"),
children: Vec::new(),
}, },
], }],
updated_instances: Vec::new(), updated_instances: Vec::new(),
removed_instances: Vec::new(), removed_instances: Vec::new(),
}; };

View File

@@ -1,20 +1,15 @@
use std::{ use std::{borrow::Cow, collections::BTreeMap};
borrow::Cow,
collections::BTreeMap,
};
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::{RbxTree, RbxValue, RbxId}; use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry}, imfs::new::{Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotCsv; pub struct SnapshotCsv;
@@ -27,16 +22,18 @@ impl SnapshotMiddleware for SnapshotCsv {
return Ok(None); return Ok(None);
} }
let file_name = entry.path() let file_name = entry.path().file_name().unwrap().to_string_lossy();
.file_name().unwrap().to_string_lossy();
if !file_name.ends_with(".csv") { if !file_name.ends_with(".csv") {
return Ok(None); return Ok(None);
} }
let instance_name = entry.path() let instance_name = entry
.file_stem().expect("Could not extract file stem") .path()
.to_string_lossy().to_string(); .file_stem()
.expect("Could not extract file stem")
.to_string_lossy()
.to_string();
let table_contents = convert_localization_csv(entry.contents(imfs)?); let table_contents = convert_localization_csv(entry.contents(imfs)?);
@@ -53,10 +50,7 @@ impl SnapshotMiddleware for SnapshotCsv {
})) }))
} }
fn from_instance( fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
unimplemented!("Snapshotting CSV localization tables"); unimplemented!("Snapshotting CSV localization tables");
} }
} }
@@ -96,15 +90,12 @@ struct LocalizationEntry<'a> {
fn convert_localization_csv(contents: &[u8]) -> String { fn convert_localization_csv(contents: &[u8]) -> String {
let mut reader = csv::Reader::from_reader(contents); let mut reader = csv::Reader::from_reader(contents);
let headers = reader.headers() let headers = reader.headers().expect("TODO: Handle csv errors").clone();
.expect("TODO: Handle csv errors")
.clone();
let mut records = Vec::new(); let mut records = Vec::new();
for record in reader.into_records() { for record in reader.into_records() {
let record = record let record = record.expect("TODO: Handle csv errors");
.expect("TODO: Handle csv errors");
records.push(record); records.push(record);
} }
@@ -137,8 +128,7 @@ fn convert_localization_csv(contents: &[u8]) -> String {
entries.push(entry); entries.push(entry);
} }
serde_json::to_string(&entries) serde_json::to_string(&entries).expect("Could not encode JSON for localization table")
.expect("Could not encode JSON for localization table")
} }
#[cfg(test)] #[cfg(test)]
@@ -150,9 +140,11 @@ mod test {
#[test] #[test]
fn csv_from_imfs() { fn csv_from_imfs() {
let mut imfs = Imfs::new(NoopFetcher); let mut imfs = Imfs::new(NoopFetcher);
let file = ImfsSnapshot::file(r#" let file = ImfsSnapshot::file(
r#"
Key,Source,Context,Example,es Key,Source,Context,Example,es
Ack,Ack!,,An exclamation of despair,¡Ay!"#); Ack,Ack!,,An exclamation of despair,¡Ay!"#,
);
imfs.load_from_snapshot("/foo.csv", file); imfs.load_from_snapshot("/foo.csv", file);
@@ -165,10 +157,13 @@ Ack,Ack!,,An exclamation of despair,¡Ay!"#);
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "LocalizationTable"); assert_eq!(instance_snapshot.class_name, "LocalizationTable");
assert_eq!(instance_snapshot.children, Vec::new()); assert_eq!(instance_snapshot.children, Vec::new());
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Contents".to_owned() => RbxValue::String { instance_snapshot.properties,
value: expected_contents.to_owned(), hashmap! {
}, "Contents".to_owned() => RbxValue::String {
}); value: expected_contents.to_owned(),
},
}
);
} }
} }

View File

@@ -1,19 +1,15 @@
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use rbx_dom_weak::{RbxTree, RbxId}; use rbx_dom_weak::{RbxId, RbxTree};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsSnapshot, DirectorySnapshot, ImfsFetcher, ImfsEntry}, imfs::new::{DirectorySnapshot, Imfs, ImfsEntry, ImfsFetcher, ImfsSnapshot},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::{
snapshot_from_imfs, middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware},
snapshot_from_instance, snapshot_from_imfs, snapshot_from_instance,
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
}; };
pub struct SnapshotDir; pub struct SnapshotDir;
@@ -37,9 +33,13 @@ impl SnapshotMiddleware for SnapshotDir {
} }
} }
let instance_name = entry.path() let instance_name = entry
.file_name().expect("Could not extract file name") .path()
.to_str().unwrap().to_string(); .file_name()
.expect("Could not extract file name")
.to_str()
.unwrap()
.to_string();
Ok(Some(InstanceSnapshot { Ok(Some(InstanceSnapshot {
snapshot_id: None, snapshot_id: None,
@@ -50,10 +50,7 @@ impl SnapshotMiddleware for SnapshotDir {
})) }))
} }
fn from_instance( fn from_instance(tree: &RbxTree, id: RbxId) -> SnapshotFileResult {
tree: &RbxTree,
id: RbxId,
) -> SnapshotFileResult {
let instance = tree.get_instance(id).unwrap(); let instance = tree.get_instance(id).unwrap();
if instance.class_name != "Folder" { if instance.class_name != "Folder" {
@@ -68,9 +65,7 @@ impl SnapshotMiddleware for SnapshotDir {
} }
} }
let snapshot = ImfsSnapshot::Directory(DirectorySnapshot { let snapshot = ImfsSnapshot::Directory(DirectorySnapshot { children });
children,
});
Some((instance.name.clone(), snapshot)) Some((instance.name.clone(), snapshot))
} }

View File

@@ -1,12 +1,6 @@
use std::{ use std::{error::Error, fmt, path::PathBuf};
fmt,
error::Error,
path::PathBuf,
};
use crate::{ use crate::snapshot::InstanceSnapshot;
snapshot::InstanceSnapshot,
};
pub type SnapshotResult<'a> = Result<Option<InstanceSnapshot<'a>>, SnapshotError>; pub type SnapshotResult<'a> = Result<Option<InstanceSnapshot<'a>>, SnapshotError>;
@@ -43,9 +37,7 @@ impl SnapshotError {
path: impl Into<PathBuf>, path: impl Into<PathBuf>,
) -> SnapshotError { ) -> SnapshotError {
SnapshotError { SnapshotError {
detail: SnapshotErrorDetail::FileContentsBadUnicode { detail: SnapshotErrorDetail::FileContentsBadUnicode { inner },
inner,
},
path: Some(path.into()), path: Some(path.into()),
} }
} }
@@ -70,9 +62,7 @@ impl fmt::Display for SnapshotError {
pub enum SnapshotErrorDetail { pub enum SnapshotErrorDetail {
FileDidNotExist, FileDidNotExist,
FileNameBadUnicode, FileNameBadUnicode,
FileContentsBadUnicode { FileContentsBadUnicode { inner: std::str::Utf8Error },
inner: std::str::Utf8Error,
},
} }
impl SnapshotErrorDetail { impl SnapshotErrorDetail {
@@ -81,7 +71,7 @@ impl SnapshotErrorDetail {
match self { match self {
FileContentsBadUnicode { inner } => Some(inner), FileContentsBadUnicode { inner } => Some(inner),
_ => None _ => None,
} }
} }
} }
@@ -93,7 +83,9 @@ impl fmt::Display for SnapshotErrorDetail {
match self { match self {
FileDidNotExist => write!(formatter, "file did not exist"), FileDidNotExist => write!(formatter, "file did not exist"),
FileNameBadUnicode => write!(formatter, "file name had malformed Unicode"), FileNameBadUnicode => write!(formatter, "file name had malformed Unicode"),
FileContentsBadUnicode { inner } => write!(formatter, "file had malformed unicode: {}", inner), FileContentsBadUnicode { inner } => {
write!(formatter, "file had malformed unicode: {}", inner)
}
} }
} }
} }

View File

@@ -1,20 +1,15 @@
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use rbx_dom_weak::{RbxId, RbxTree, UnresolvedRbxValue};
use rbx_reflection::try_resolve_value; use rbx_reflection::try_resolve_value;
use rbx_dom_weak::{RbxTree, RbxId, UnresolvedRbxValue}; use serde::Deserialize;
use serde::{Deserialize};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry}, imfs::new::{Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotJsonModel; pub struct SnapshotJsonModel;
@@ -27,22 +22,30 @@ impl SnapshotMiddleware for SnapshotJsonModel {
return Ok(None); return Ok(None);
} }
let file_name = entry.path() let file_name = entry.path().file_name().unwrap().to_string_lossy();
.file_name().unwrap().to_string_lossy();
let instance_name = match match_trailing(&file_name, ".model.json") { let instance_name = match match_trailing(&file_name, ".model.json") {
Some(name) => name.to_owned(), Some(name) => name.to_owned(),
None => return Ok(None), None => return Ok(None),
}; };
let instance: JsonModel = serde_json::from_slice(entry.contents(imfs)?) let instance: JsonModel =
.expect("TODO: Handle serde_json errors"); serde_json::from_slice(entry.contents(imfs)?).expect("TODO: Handle serde_json errors");
if let Some(json_name) = &instance.name { if let Some(json_name) = &instance.name {
if json_name != &instance_name { if json_name != &instance_name {
log::warn!("Name from JSON model did not match its file name: {}", entry.path().display()); log::warn!(
log::warn!("In Rojo < alpha 14, this model is named \"{}\" (from its 'Name' property)", json_name); "Name from JSON model did not match its file name: {}",
log::warn!("In Rojo >= alpha 14, this model is named \"{}\" (from its file name)", instance_name); entry.path().display()
);
log::warn!(
"In Rojo < alpha 14, this model is named \"{}\" (from its 'Name' property)",
json_name
);
log::warn!(
"In Rojo >= alpha 14, this model is named \"{}\" (from its file name)",
instance_name
);
log::warn!("'Name' for the top-level instance in a JSON model is now optional and will be ignored."); log::warn!("'Name' for the top-level instance in a JSON model is now optional and will be ignored.");
} }
} }
@@ -52,10 +55,7 @@ impl SnapshotMiddleware for SnapshotJsonModel {
Ok(Some(snapshot)) Ok(Some(snapshot))
} }
fn from_instance( fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
unimplemented!("Snapshotting models"); unimplemented!("Snapshotting models");
} }
} }
@@ -103,14 +103,17 @@ impl JsonModelCore {
fn into_snapshot(self, name: String) -> InstanceSnapshot<'static> { fn into_snapshot(self, name: String) -> InstanceSnapshot<'static> {
let class_name = self.class_name; let class_name = self.class_name;
let children = self.children.into_iter() let children = self
.children
.into_iter()
.map(|child| child.core.into_snapshot(child.name)) .map(|child| child.core.into_snapshot(child.name))
.collect(); .collect();
let properties = self.properties.into_iter() let properties = self
.properties
.into_iter()
.map(|(key, value)| { .map(|(key, value)| {
try_resolve_value(&class_name, &key, &value) try_resolve_value(&class_name, &key, &value).map(|resolved| (key, resolved))
.map(|resolved| (key, resolved))
}) })
.collect::<Result<HashMap<_, _>, _>>() .collect::<Result<HashMap<_, _>, _>>()
.expect("TODO: Handle rbx_reflection errors"); .expect("TODO: Handle rbx_reflection errors");
@@ -137,7 +140,8 @@ mod test {
#[test] #[test]
fn model_from_imfs() { fn model_from_imfs() {
let mut imfs = Imfs::new(NoopFetcher); let mut imfs = Imfs::new(NoopFetcher);
let file = ImfsSnapshot::file(r#" let file = ImfsSnapshot::file(
r#"
{ {
"Name": "children", "Name": "children",
"ClassName": "IntValue", "ClassName": "IntValue",
@@ -151,31 +155,35 @@ mod test {
} }
] ]
} }
"#); "#,
);
imfs.load_from_snapshot("/foo.model.json", file); imfs.load_from_snapshot("/foo.model.json", file);
let entry = imfs.get("/foo.model.json").unwrap(); let entry = imfs.get("/foo.model.json").unwrap();
let instance_snapshot = SnapshotJsonModel::from_imfs(&mut imfs, &entry).unwrap().unwrap(); let instance_snapshot = SnapshotJsonModel::from_imfs(&mut imfs, &entry)
.unwrap()
.unwrap();
assert_eq!(instance_snapshot, InstanceSnapshot { assert_eq!(
snapshot_id: None, instance_snapshot,
name: Cow::Borrowed("foo"), InstanceSnapshot {
class_name: Cow::Borrowed("IntValue"), snapshot_id: None,
properties: hashmap! { name: Cow::Borrowed("foo"),
"Value".to_owned() => RbxValue::Int32 { class_name: Cow::Borrowed("IntValue"),
value: 5, properties: hashmap! {
"Value".to_owned() => RbxValue::Int32 {
value: 5,
},
}, },
}, children: vec![InstanceSnapshot {
children: vec![
InstanceSnapshot {
snapshot_id: None, snapshot_id: None,
name: Cow::Borrowed("The Child"), name: Cow::Borrowed("The Child"),
class_name: Cow::Borrowed("StringValue"), class_name: Cow::Borrowed("StringValue"),
properties: HashMap::new(), properties: HashMap::new(),
children: Vec::new(), children: Vec::new(),
}, },],
], }
}); );
} }
} }

View File

@@ -1,19 +1,14 @@
use std::{ use std::{borrow::Cow, str};
borrow::Cow,
str,
};
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::{RbxTree, RbxValue, RbxId}; use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry, FsResultExt}, imfs::new::{FsResultExt, Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotLua; pub struct SnapshotLua;
@@ -22,8 +17,7 @@ impl SnapshotMiddleware for SnapshotLua {
imfs: &mut Imfs<F>, imfs: &mut Imfs<F>,
entry: &ImfsEntry, entry: &ImfsEntry,
) -> SnapshotInstanceResult<'static> { ) -> SnapshotInstanceResult<'static> {
let file_name = entry.path() let file_name = entry.path().file_name().unwrap().to_string_lossy();
.file_name().unwrap().to_string_lossy();
if entry.is_directory() { if entry.is_directory() {
let module_init_path = entry.path().join("init.lua"); let module_init_path = entry.path().join("init.lua");
@@ -54,15 +48,16 @@ impl SnapshotMiddleware for SnapshotLua {
} }
} }
let (class_name, instance_name) = if let Some(name) = match_trailing(&file_name, ".server.lua") { let (class_name, instance_name) =
("Script", name) if let Some(name) = match_trailing(&file_name, ".server.lua") {
} else if let Some(name) = match_trailing(&file_name, ".client.lua") { ("Script", name)
("LocalScript", name) } else if let Some(name) = match_trailing(&file_name, ".client.lua") {
} else if let Some(name) = match_trailing(&file_name, ".lua") { ("LocalScript", name)
("ModuleScript", name) } else if let Some(name) = match_trailing(&file_name, ".lua") {
} else { ("ModuleScript", name)
return Ok(None); } else {
}; return Ok(None);
};
let contents = entry.contents(imfs)?; let contents = entry.contents(imfs)?;
let contents_str = str::from_utf8(contents) let contents_str = str::from_utf8(contents)
@@ -84,14 +79,13 @@ impl SnapshotMiddleware for SnapshotLua {
})) }))
} }
fn from_instance( fn from_instance(tree: &RbxTree, id: RbxId) -> SnapshotFileResult {
tree: &RbxTree,
id: RbxId,
) -> SnapshotFileResult {
let instance = tree.get_instance(id).unwrap(); let instance = tree.get_instance(id).unwrap();
match instance.class_name.as_str() { match instance.class_name.as_str() {
"ModuleScript" | "LocalScript" | "Script" => unimplemented!("Snapshotting Script instances"), "ModuleScript" | "LocalScript" | "Script" => {
unimplemented!("Snapshotting Script instances")
}
_ => None, _ => None,
} }
} }
@@ -126,11 +120,14 @@ mod test {
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "ModuleScript"); assert_eq!(instance_snapshot.class_name, "ModuleScript");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Source".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello there!".to_owned(), hashmap! {
}, "Source".to_owned() => RbxValue::String {
}); value: "Hello there!".to_owned(),
},
}
);
} }
#[test] #[test]
@@ -145,11 +142,14 @@ mod test {
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "Script"); assert_eq!(instance_snapshot.class_name, "Script");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Source".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello there!".to_owned(), hashmap! {
}, "Source".to_owned() => RbxValue::String {
}); value: "Hello there!".to_owned(),
},
}
);
} }
#[test] #[test]
@@ -164,10 +164,13 @@ mod test {
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "LocalScript"); assert_eq!(instance_snapshot.class_name, "LocalScript");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Source".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello there!".to_owned(), hashmap! {
}, "Source".to_owned() => RbxValue::String {
}); value: "Hello there!".to_owned(),
},
}
);
} }
} }

View File

@@ -1,18 +1,11 @@
use std::{ use std::path::{Path, PathBuf};
path::{PathBuf, Path},
};
use rbx_dom_weak::{RbxTree, RbxId}; use rbx_dom_weak::{RbxId, RbxTree};
use crate::{ use crate::{
imfs::{ imfs::{
new::{Imfs, ImfsEntry, ImfsFetcher, ImfsSnapshot},
FsResult, FsResult,
new::{
Imfs,
ImfsEntry,
ImfsFetcher,
ImfsSnapshot,
},
}, },
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
@@ -26,14 +19,9 @@ pub trait SnapshotMiddleware {
entry: &ImfsEntry, entry: &ImfsEntry,
) -> SnapshotInstanceResult<'static>; ) -> SnapshotInstanceResult<'static>;
fn from_instance( fn from_instance(tree: &RbxTree, id: RbxId) -> SnapshotFileResult;
tree: &RbxTree,
id: RbxId,
) -> SnapshotFileResult;
fn change_affects_paths( fn change_affects_paths(path: &Path) -> Vec<PathBuf> {
path: &Path
) -> Vec<PathBuf> {
vec![path.to_path_buf()] vec![path.to_path_buf()]
} }
} }

View File

@@ -15,20 +15,20 @@ mod rbxm;
mod rbxmx; mod rbxmx;
mod txt; mod txt;
use rbx_dom_weak::{RbxTree, RbxId}; use rbx_dom_weak::{RbxId, RbxTree};
use crate::imfs::new::{Imfs, ImfsEntry, ImfsFetcher};
use self::{ use self::{
middleware::{SnapshotInstanceResult, SnapshotFileResult, SnapshotMiddleware},
csv::SnapshotCsv, csv::SnapshotCsv,
dir::SnapshotDir, dir::SnapshotDir,
json_model::SnapshotJsonModel, json_model::SnapshotJsonModel,
lua::SnapshotLua, lua::SnapshotLua,
middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware},
project::SnapshotProject, project::SnapshotProject,
rbxm::SnapshotRbxm, rbxm::SnapshotRbxm,
rbxmx::SnapshotRbxmx, rbxmx::SnapshotRbxmx,
txt::SnapshotTxt, txt::SnapshotTxt,
}; };
use crate::imfs::new::{Imfs, ImfsEntry, ImfsFetcher};
macro_rules! middlewares { macro_rules! middlewares {
( $($middleware: ident,)* ) => { ( $($middleware: ident,)* ) => {

View File

@@ -1,23 +1,20 @@
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use rbx_dom_weak::{RbxTree, RbxId}; use rbx_dom_weak::{RbxId, RbxTree};
use rbx_reflection::try_resolve_value; use rbx_reflection::try_resolve_value;
use crate::{ use crate::{
project::{Project, ProjectNode},
imfs::{ imfs::{
new::{Imfs, ImfsEntry, ImfsFetcher},
FsErrorKind, FsErrorKind,
new::{Imfs, ImfsFetcher, ImfsEntry},
}, },
project::{Project, ProjectNode},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::{
middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware},
snapshot_from_imfs, snapshot_from_imfs,
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
}; };
pub struct SnapshotProject; pub struct SnapshotProject;
@@ -38,7 +35,7 @@ impl SnapshotMiddleware for SnapshotProject {
} }
if !entry.path().to_string_lossy().ends_with(".project.json") { if !entry.path().to_string_lossy().ends_with(".project.json") {
return Ok(None) return Ok(None);
} }
let project = Project::load_from_slice(entry.contents(imfs)?, entry.path()) let project = Project::load_from_slice(entry.contents(imfs)?, entry.path())
@@ -47,10 +44,7 @@ impl SnapshotMiddleware for SnapshotProject {
snapshot_project_node(&project.name, &project.tree, imfs) snapshot_project_node(&project.name, &project.tree, imfs)
} }
fn from_instance( fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
// TODO: Supporting turning instances into projects // TODO: Supporting turning instances into projects
None None
} }
@@ -61,10 +55,14 @@ fn snapshot_project_node<F: ImfsFetcher>(
node: &ProjectNode, node: &ProjectNode,
imfs: &mut Imfs<F>, imfs: &mut Imfs<F>,
) -> SnapshotInstanceResult<'static> { ) -> SnapshotInstanceResult<'static> {
assert!(node.ignore_unknown_instances.is_none(), "TODO: Support $ignoreUnknownInstances"); assert!(
node.ignore_unknown_instances.is_none(),
"TODO: Support $ignoreUnknownInstances"
);
let name = Cow::Owned(instance_name.to_owned()); let name = Cow::Owned(instance_name.to_owned());
let mut class_name = node.class_name let mut class_name = node
.class_name
.as_ref() .as_ref()
.map(|name| Cow::Owned(name.clone())); .map(|name| Cow::Owned(name.clone()));
let mut properties = HashMap::new(); let mut properties = HashMap::new();
@@ -90,7 +88,7 @@ fn snapshot_project_node<F: ImfsFetcher>(
panic!("If $className and $path are specified, $path must yield an instance of class Folder"); panic!("If $className and $path are specified, $path must yield an instance of class Folder");
} }
} }
None => Some(snapshot.class_name) None => Some(snapshot.class_name),
}; };
// Properties from the snapshot are pulled in unchanged, and // Properties from the snapshot are pulled in unchanged, and
@@ -108,7 +106,9 @@ fn snapshot_project_node<F: ImfsFetcher>(
} }
} else { } else {
// TODO: Should this issue an error instead? // TODO: Should this issue an error instead?
log::warn!("$path referred to a path that could not be turned into an instance by Rojo"); log::warn!(
"$path referred to a path that could not be turned into an instance by Rojo"
);
} }
} }
@@ -142,8 +142,8 @@ fn snapshot_project_node<F: ImfsFetcher>(
mod test { mod test {
use super::*; use super::*;
use rbx_dom_weak::RbxValue;
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::RbxValue;
use crate::imfs::new::{ImfsSnapshot, NoopFetcher}; use crate::imfs::new::{ImfsSnapshot, NoopFetcher};
@@ -236,11 +236,14 @@ mod test {
assert_eq!(instance_snapshot.name, "resolved-properties"); assert_eq!(instance_snapshot.name, "resolved-properties");
assert_eq!(instance_snapshot.class_name, "StringValue"); assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Value".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello, world!".to_owned(), hashmap! {
}, "Value".to_owned() => RbxValue::String {
}); value: "Hello, world!".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new()); assert_eq!(instance_snapshot.children, Vec::new());
} }
@@ -272,11 +275,14 @@ mod test {
assert_eq!(instance_snapshot.name, "unresolved-properties"); assert_eq!(instance_snapshot.name, "unresolved-properties");
assert_eq!(instance_snapshot.class_name, "StringValue"); assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Value".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hi!".to_owned(), hashmap! {
}, "Value".to_owned() => RbxValue::String {
}); value: "Hi!".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new()); assert_eq!(instance_snapshot.children, Vec::new());
} }
@@ -345,11 +351,14 @@ mod test {
assert_eq!(instance_snapshot.name, "path-project"); assert_eq!(instance_snapshot.name, "path-project");
assert_eq!(instance_snapshot.class_name, "StringValue"); assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Value".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello, world!".to_owned(), hashmap! {
}, "Value".to_owned() => RbxValue::String {
}); value: "Hello, world!".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new()); assert_eq!(instance_snapshot.children, Vec::new());
} }
@@ -479,11 +488,14 @@ mod test {
assert_eq!(instance_snapshot.name, "path-property-override"); assert_eq!(instance_snapshot.name, "path-property-override");
assert_eq!(instance_snapshot.class_name, "StringValue"); assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Value".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Changed".to_owned(), hashmap! {
}, "Value".to_owned() => RbxValue::String {
}); value: "Changed".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new()); assert_eq!(instance_snapshot.children, Vec::new());
} }
} }

View File

@@ -1,18 +1,13 @@
use std::{ use std::{borrow::Cow, collections::HashMap};
borrow::Cow,
collections::HashMap,
};
use rbx_dom_weak::{RbxTree, RbxInstanceProperties, RbxId}; use rbx_dom_weak::{RbxId, RbxInstanceProperties, RbxTree};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry}, imfs::new::{Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotRbxm; pub struct SnapshotRbxm;
@@ -25,16 +20,18 @@ impl SnapshotMiddleware for SnapshotRbxm {
return Ok(None); return Ok(None);
} }
let file_name = entry.path() let file_name = entry.path().file_name().unwrap().to_string_lossy();
.file_name().unwrap().to_string_lossy();
if !file_name.ends_with(".rbxm") { if !file_name.ends_with(".rbxm") {
return Ok(None); return Ok(None);
} }
let instance_name = entry.path() let instance_name = entry
.file_stem().expect("Could not extract file stem") .path()
.to_string_lossy().to_string(); .file_stem()
.expect("Could not extract file stem")
.to_string_lossy()
.to_string();
let mut temp_tree = RbxTree::new(RbxInstanceProperties { let mut temp_tree = RbxTree::new(RbxInstanceProperties {
name: "DataModel".to_owned(), name: "DataModel".to_owned(),
@@ -59,10 +56,7 @@ impl SnapshotMiddleware for SnapshotRbxm {
} }
} }
fn from_instance( fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
unimplemented!("Snapshotting models"); unimplemented!("Snapshotting models");
} }
} }

View File

@@ -1,15 +1,13 @@
use std::borrow::Cow; use std::borrow::Cow;
use rbx_dom_weak::{RbxTree, RbxId}; use rbx_dom_weak::{RbxId, RbxTree};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry}, imfs::new::{Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotRbxmx; pub struct SnapshotRbxmx;
@@ -22,16 +20,18 @@ impl SnapshotMiddleware for SnapshotRbxmx {
return Ok(None); return Ok(None);
} }
let file_name = entry.path() let file_name = entry.path().file_name().unwrap().to_string_lossy();
.file_name().unwrap().to_string_lossy();
if !file_name.ends_with(".rbxmx") { if !file_name.ends_with(".rbxmx") {
return Ok(None); return Ok(None);
} }
let instance_name = entry.path() let instance_name = entry
.file_stem().expect("Could not extract file stem") .path()
.to_string_lossy().to_string(); .file_stem()
.expect("Could not extract file stem")
.to_string_lossy()
.to_string();
let options = rbx_xml::DecodeOptions::new() let options = rbx_xml::DecodeOptions::new()
.property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown); .property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown);
@@ -52,10 +52,7 @@ impl SnapshotMiddleware for SnapshotRbxmx {
} }
} }
fn from_instance( fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
unimplemented!("Snapshotting models"); unimplemented!("Snapshotting models");
} }
} }
@@ -71,7 +68,8 @@ mod test {
#[test] #[test]
fn model_from_imfs() { fn model_from_imfs() {
let mut imfs = Imfs::new(NoopFetcher); let mut imfs = Imfs::new(NoopFetcher);
let file = ImfsSnapshot::file(r#" let file = ImfsSnapshot::file(
r#"
<roblox version="4"> <roblox version="4">
<Item class="Folder" referent="0"> <Item class="Folder" referent="0">
<Properties> <Properties>
@@ -79,12 +77,15 @@ mod test {
</Properties> </Properties>
</Item> </Item>
</roblox> </roblox>
"#); "#,
);
imfs.load_from_snapshot("/foo.rbxmx", file); imfs.load_from_snapshot("/foo.rbxmx", file);
let entry = imfs.get("/foo.rbxmx").unwrap(); let entry = imfs.get("/foo.rbxmx").unwrap();
let instance_snapshot = SnapshotRbxmx::from_imfs(&mut imfs, &entry).unwrap().unwrap(); let instance_snapshot = SnapshotRbxmx::from_imfs(&mut imfs, &entry)
.unwrap()
.unwrap();
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "Folder"); assert_eq!(instance_snapshot.class_name, "Folder");

View File

@@ -1,19 +1,14 @@
use std::{ use std::{borrow::Cow, str};
borrow::Cow,
str,
};
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::{RbxTree, RbxValue, RbxId}; use rbx_dom_weak::{RbxId, RbxTree, RbxValue};
use crate::{ use crate::{
imfs::new::{Imfs, ImfsSnapshot, FileSnapshot, ImfsFetcher, ImfsEntry}, imfs::new::{FileSnapshot, Imfs, ImfsEntry, ImfsFetcher, ImfsSnapshot},
snapshot::InstanceSnapshot, snapshot::InstanceSnapshot,
}; };
use super::{ use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotTxt; pub struct SnapshotTxt;
@@ -35,13 +30,18 @@ impl SnapshotMiddleware for SnapshotTxt {
return Ok(None); return Ok(None);
} }
let instance_name = entry.path() let instance_name = entry
.file_stem().expect("Could not extract file stem") .path()
.to_str().unwrap().to_string(); .file_stem()
.expect("Could not extract file stem")
.to_str()
.unwrap()
.to_string();
let contents = entry.contents(imfs)?; let contents = entry.contents(imfs)?;
let contents_str = str::from_utf8(contents) let contents_str = str::from_utf8(contents)
.expect("File content was not valid UTF-8").to_string(); .expect("File content was not valid UTF-8")
.to_string();
let properties = hashmap! { let properties = hashmap! {
"Value".to_owned() => RbxValue::String { "Value".to_owned() => RbxValue::String {
@@ -58,10 +58,7 @@ impl SnapshotMiddleware for SnapshotTxt {
})) }))
} }
fn from_instance( fn from_instance(tree: &RbxTree, id: RbxId) -> SnapshotFileResult {
tree: &RbxTree,
id: RbxId,
) -> SnapshotFileResult {
let instance = tree.get_instance(id).unwrap(); let instance = tree.get_instance(id).unwrap();
if instance.class_name != "StringValue" { if instance.class_name != "StringValue" {
@@ -94,7 +91,7 @@ mod test {
use super::*; use super::*;
use maplit::hashmap; use maplit::hashmap;
use rbx_dom_weak::{RbxInstanceProperties}; use rbx_dom_weak::RbxInstanceProperties;
use crate::imfs::new::NoopFetcher; use crate::imfs::new::NoopFetcher;
@@ -110,11 +107,14 @@ mod test {
assert_eq!(instance_snapshot.name, "foo"); assert_eq!(instance_snapshot.name, "foo");
assert_eq!(instance_snapshot.class_name, "StringValue"); assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! { assert_eq!(
"Value".to_owned() => RbxValue::String { instance_snapshot.properties,
value: "Hello there!".to_owned(), hashmap! {
}, "Value".to_owned() => RbxValue::String {
}); value: "Hello there!".to_owned(),
},
}
);
} }
#[test] #[test]

View File

@@ -1,32 +1,15 @@
//! Defines Rojo's HTTP API, all under /api. These endpoints generally return //! Defines Rojo's HTTP API, all under /api. These endpoints generally return
//! JSON. //! JSON.
use std::{ use std::{collections::HashSet, sync::Arc};
collections::HashSet,
sync::Arc,
};
use futures::{ use futures::{future, Future};
future,
Future,
};
use hyper::{ use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
service::Service,
header,
StatusCode,
Method,
Body,
Request,
Response,
};
use serde::{Serialize, Deserialize};
use rbx_dom_weak::RbxId; use rbx_dom_weak::RbxId;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{serve_session::ServeSession, session_id::SessionId};
serve_session::ServeSession,
session_id::SessionId,
};
const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION"); const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
const PROTOCOL_VERSION: u64 = 3; const PROTOCOL_VERSION: u64 = 3;
@@ -83,7 +66,8 @@ impl Service for ApiService {
type ReqBody = Body; type ReqBody = Body;
type ResBody = Body; type ResBody = Body;
type Error = hyper::Error; type Error = hyper::Error;
type Future = Box<dyn Future<Item = hyper::Response<Self::ReqBody>, Error = Self::Error> + Send>; type Future =
Box<dyn Future<Item = hyper::Response<Self::ReqBody>, Error = Self::Error> + Send>;
fn call(&mut self, request: hyper::Request<Self::ReqBody>) -> Self::Future { fn call(&mut self, request: hyper::Request<Self::ReqBody>) -> Self::Future {
let response = match (request.method(), request.uri().path()) { let response = match (request.method(), request.uri().path()) {
@@ -92,12 +76,10 @@ impl Service for ApiService {
(&Method::GET, path) if path.starts_with("/api/subscribe/") => { (&Method::GET, path) if path.starts_with("/api/subscribe/") => {
return self.handle_api_subscribe(request); return self.handle_api_subscribe(request);
} }
_ => { _ => Response::builder()
Response::builder() .status(StatusCode::NOT_FOUND)
.status(StatusCode::NOT_FOUND) .body(Body::empty())
.body(Body::empty()) .unwrap(),
.unwrap()
}
}; };
Box::new(future::ok(response)) Box::new(future::ok(response))
@@ -106,9 +88,7 @@ impl Service for ApiService {
impl ApiService { impl ApiService {
pub fn new(serve_session: Arc<ServeSession>) -> ApiService { pub fn new(serve_session: Arc<ServeSession>) -> ApiService {
ApiService { ApiService { serve_session }
serve_session,
}
} }
/// Get a summary of information about the server /// Get a summary of information about the server
@@ -128,12 +108,14 @@ impl ApiService {
let _cursor: u32 = match argument.parse() { let _cursor: u32 = match argument.parse() {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
return Box::new(future::ok(Response::builder() return Box::new(future::ok(
.status(StatusCode::BAD_REQUEST) Response::builder()
.header(header::CONTENT_TYPE, "text/plain") .status(StatusCode::BAD_REQUEST)
.body(Body::from(err.to_string())) .header(header::CONTENT_TYPE, "text/plain")
.unwrap())); .body(Body::from(err.to_string()))
}, .unwrap(),
));
}
}; };
Box::new(future::ok(response_json(SubscribeResponse { Box::new(future::ok(response_json(SubscribeResponse {
@@ -143,10 +125,7 @@ impl ApiService {
fn handle_api_read(&self, request: Request<Body>) -> Response<Body> { fn handle_api_read(&self, request: Request<Body>) -> Response<Body> {
let argument = &request.uri().path()["/api/read/".len()..]; let argument = &request.uri().path()["/api/read/".len()..];
let requested_ids: Option<Vec<RbxId>> = argument let requested_ids: Option<Vec<RbxId>> = argument.split(',').map(RbxId::parse_str).collect();
.split(',')
.map(RbxId::parse_str)
.collect();
let _requested_ids = match requested_ids { let _requested_ids = match requested_ids {
Some(id) => id, Some(id) => id,
@@ -156,7 +135,7 @@ impl ApiService {
.header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_TYPE, "text/plain")
.body(Body::from("Malformed ID list")) .body(Body::from("Malformed ID list"))
.unwrap(); .unwrap();
}, }
}; };
response_json(ReadResponse { response_json(ReadResponse {

View File

@@ -3,15 +3,7 @@
use std::sync::Arc; use std::sync::Arc;
use futures::{future, Future}; use futures::{future, Future};
use hyper::{ use hyper::{header, service::Service, Body, Method, Request, Response, StatusCode};
service::Service,
header,
Body,
Method,
StatusCode,
Request,
Response,
};
use ritz::html; use ritz::html;
use crate::serve_session::ServeSession; use crate::serve_session::ServeSession;
@@ -47,9 +39,7 @@ impl Service for InterfaceService {
impl InterfaceService { impl InterfaceService {
pub fn new(serve_session: Arc<ServeSession>) -> InterfaceService { pub fn new(serve_session: Arc<ServeSession>) -> InterfaceService {
InterfaceService { InterfaceService { serve_session }
serve_session,
}
} }
fn handle_home(&self) -> Response<Body> { fn handle_home(&self) -> Response<Body> {

View File

@@ -3,25 +3,16 @@ mod interface;
use std::sync::Arc; use std::sync::Arc;
use log::trace;
use futures::{ use futures::{
future::{self, FutureResult}, future::{self, FutureResult},
Future, Future,
}; };
use hyper::{ use hyper::{service::Service, Body, Request, Response, Server};
service::Service, use log::trace;
Body,
Request,
Response,
Server,
};
use crate::serve_session::ServeSession; use crate::serve_session::ServeSession;
use self::{ use self::{api::ApiService, interface::InterfaceService};
api::ApiService,
interface::InterfaceService,
};
pub struct RootService { pub struct RootService {
api: api::ApiService, api: api::ApiService,
@@ -60,9 +51,7 @@ pub struct LiveServer {
impl LiveServer { impl LiveServer {
pub fn new(serve_session: Arc<ServeSession>) -> LiveServer { pub fn new(serve_session: Arc<ServeSession>) -> LiveServer {
LiveServer { LiveServer { serve_session }
serve_session,
}
} }
pub fn start(self, port: u16) { pub fn start(self, port: u16) {

View File

@@ -1,21 +1,19 @@
#[macro_use] extern crate lazy_static; #[macro_use]
extern crate lazy_static;
use std::{ use std::{
collections::{HashMap, BTreeMap}, collections::{BTreeMap, HashMap},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rbx_dom_weak::RbxValue; use rbx_dom_weak::RbxValue;
use librojo::{ use librojo::project::{Project, ProjectNode};
project::{Project, ProjectNode},
};
lazy_static! { lazy_static! {
static ref TEST_PROJECTS_ROOT: PathBuf = { static ref TEST_PROJECTS_ROOT: PathBuf =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../test-projects") { Path::new(env!("CARGO_MANIFEST_DIR")).join("../test-projects") };
};
} }
#[test] #[test]
@@ -63,9 +61,10 @@ fn single_partition_game() {
}; };
let mut http_service_properties = HashMap::new(); let mut http_service_properties = HashMap::new();
http_service_properties.insert("HttpEnabled".to_string(), RbxValue::Bool { http_service_properties.insert(
value: true, "HttpEnabled".to_string(),
}.into()); RbxValue::Bool { value: true }.into(),
);
let http_service = ProjectNode { let http_service = ProjectNode {
class_name: Some(String::from("HttpService")), class_name: Some(String::from("HttpService")),