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::{
fs,
path::Path,
process::Command,
};
use std::{fs, path::Path, process::Command};
use insta::assert_snapshot_matches;
use tempfile::tempdir;
@@ -67,7 +63,10 @@ fn run_build_test(test_name: &str) {
let status = Command::new(exe_path)
.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")
.current_dir(working_dir)
@@ -76,8 +75,7 @@ fn run_build_test(test_name: &str) {
assert!(status.success(), "Rojo did not exit successfully");
let contents = fs::read_to_string(&output_path)
.expect("Couldn't read output file");
let contents = fs::read_to_string(&output_path).expect("Couldn't read output file");
assert_snapshot_matches!(test_name, contents);
}
}

View File

@@ -1,2 +1,2 @@
#[cfg(test)]
mod build_test;
mod build_test;

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

View File

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

View File

@@ -1,6 +1,4 @@
use std::{
path::PathBuf,
};
use std::path::PathBuf;
use failure::Fail;
@@ -8,11 +6,14 @@ use crate::project::{Project, ProjectInitError};
#[derive(Debug, Fail)]
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),
#[fail(display = "Project init error: {}", _0)]
ProjectInitError(#[fail(cause)] ProjectInitError)
ProjectInitError(#[fail(cause)] ProjectInitError),
}
impl_from!(InitError {
@@ -30,15 +31,19 @@ pub fn init(options: &InitOptions) -> Result<(), InitError> {
Some("place") | None => {
let path = Project::init_place(&options.fuzzy_project_path)?;
(path, "place")
},
}
Some("model") => {
let path = Project::init_model(&options.fuzzy_project_path)?;
(path, "model")
},
}
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(())
}
}

View File

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

View File

@@ -1,11 +1,7 @@
use std::{
collections::HashMap,
path::PathBuf,
sync::Arc,
};
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use rbx_dom_weak::{RbxTree, RbxInstanceProperties};
use failure::Fail;
use rbx_dom_weak::{RbxInstanceProperties, RbxTree};
use crate::{
imfs::new::{Imfs, RealFetcher, WatchMode},
@@ -41,8 +37,11 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
Err(other) => return Err(other.into()),
};
let port = options.port
.or(maybe_project.as_ref().and_then(|project| project.serve_port))
let port = options
.port
.or(maybe_project
.as_ref()
.and_then(|project| project.serve_port))
.unwrap_or(DEFAULT_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 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");
let snapshot = snapshot_from_imfs(&mut imfs, &entry)
@@ -86,4 +86,4 @@ pub fn serve(options: &ServeOptions) -> Result<(), ServeError> {
// }
Ok(())
}
}

View File

@@ -18,4 +18,4 @@ pub struct UploadOptions<'a> {
pub fn upload(_options: &UploadOptions) -> Result<(), UploadError> {
unimplemented!("TODO: Reimplement upload command");
}
}

View File

@@ -1,8 +1,4 @@
use std::{
io,
fmt,
path::PathBuf,
};
use std::{fmt, io, path::PathBuf};
use failure::Fail;
@@ -62,4 +58,4 @@ impl fmt::Display for FsError {
fn fmt(&self, output: &mut fmt::Formatter) -> fmt::Result {
write!(output, "{}: {}", self.path.display(), self.inner)
}
}
}

View File

@@ -29,4 +29,4 @@ pub trait ImfsFetcher {
fn watch(&mut self, path: &Path);
fn unwatch(&mut self, path: &Path);
fn receiver(&self) -> Receiver<ImfsEvent>;
}
}

View File

@@ -8,9 +8,9 @@ use crossbeam_channel::Receiver;
use crate::path_map::PathMap;
use super::{
error::{FsError, FsResult},
fetcher::{FileType, ImfsEvent, ImfsFetcher},
snapshot::ImfsSnapshot,
error::{FsResult, FsError},
fetcher::{ImfsFetcher, FileType, ImfsEvent},
};
/// An in-memory filesystem that can be incrementally populated and updated as
@@ -89,16 +89,22 @@ impl<F: ImfsFetcher> Imfs<F> {
match snapshot {
ImfsSnapshot::File(file) => {
self.inner.insert(path.to_path_buf(), ImfsItem::File(ImfsFile {
path: path.to_path_buf(),
contents: Some(file.contents),
}));
self.inner.insert(
path.to_path_buf(),
ImfsItem::File(ImfsFile {
path: path.to_path_buf(),
contents: Some(file.contents),
}),
);
}
ImfsSnapshot::Directory(directory) => {
self.inner.insert(path.to_path_buf(), ImfsItem::Directory(ImfsDirectory {
path: path.to_path_buf(),
children_enumerated: true,
}));
self.inner.insert(
path.to_path_buf(),
ImfsItem::Directory(ImfsDirectory {
path: path.to_path_buf(),
children_enumerated: true,
}),
);
for (child_name, child) in directory.children.into_iter() {
self.load_from_snapshot(path.join(child_name), child);
@@ -114,7 +120,9 @@ impl<F: ImfsFetcher> Imfs<F> {
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()))?;
match self.inner.get_mut(path) {
@@ -131,18 +139,25 @@ impl<F: ImfsFetcher> Imfs<F> {
}
(ImfsItem::File(_), FileType::Directory) => {
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);
}
(ImfsItem::Directory(_), FileType::File) => {
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);
}
}
}
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() {
ImfsItem::File(file) => {
if file.contents.is_none() {
file.contents = Some(self.fetcher.read_contents(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?);
file.contents = Some(
self.fetcher
.read_contents(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?,
);
}
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);
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.
.into_iter()
.map(PathBuf::from) // Convert paths from &Path to PathBuf
@@ -215,13 +238,17 @@ impl<F: ImfsFetcher> Imfs<F> {
.collect::<FsResult<Vec<ImfsEntry>>>();
}
self.fetcher.read_children(path)
self.fetcher
.read_children(path)
.map_err(|err| FsError::new(err, path.to_path_buf()))?
.into_iter()
.map(|path| self.get(path))
.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.
fn read_if_not_exists(&mut self, path: &Path) -> FsResult<()> {
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()))?;
if kind == FileType::Directory {
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(())
@@ -293,10 +323,7 @@ impl ImfsEntry {
imfs.get_contents(&self.path)
}
pub fn children(
&self,
imfs: &mut Imfs<impl ImfsFetcher>,
) -> FsResult<Vec<ImfsEntry>> {
pub fn children(&self, imfs: &mut Imfs<impl ImfsFetcher>) -> FsResult<Vec<ImfsEntry>> {
imfs.get_children(&self.path)
}
@@ -352,19 +379,12 @@ pub struct ImfsDirectory {
mod test {
use super::*;
use std::{
rc::Rc,
cell::RefCell,
};
use std::{cell::RefCell, rc::Rc};
use crossbeam_channel::Receiver;
use maplit::hashmap;
use super::super::{
noop_fetcher::NoopFetcher,
error::FsErrorKind,
fetcher::ImfsEvent,
};
use super::super::{error::FsErrorKind, fetcher::ImfsEvent, noop_fetcher::NoopFetcher};
#[test]
fn from_snapshot_file() {
@@ -458,11 +478,9 @@ mod test {
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> {
crossbeam_channel::never()
@@ -477,11 +495,9 @@ mod test {
inner: mock_state.clone(),
});
let a = imfs.get("/dir/a.txt")
.expect("mock file did not exist");
let a = imfs.get("/dir/a.txt").expect("mock file did not exist");
let contents = a.contents(&mut imfs)
.expect("mock file contents error");
let contents = a.contents(&mut imfs).expect("mock file contents error");
assert_eq!(contents, b"Initial contents");
@@ -493,8 +509,7 @@ mod test {
imfs.raise_file_changed("/dir/a.txt")
.expect("error processing file change");
let contents = a.contents(&mut imfs)
.expect("mock file contents error");
let contents = a.contents(&mut imfs).expect("mock file contents error");
assert_eq!(contents, b"Changed contents");
}
@@ -506,10 +521,10 @@ mod test {
let file = ImfsSnapshot::file("hello, world!");
imfs.load_from_snapshot("/hello.txt", file);
let hello = imfs.get("/hello.txt")
.expect("couldn't get hello.txt");
let hello = imfs.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");
assert_eq!(contents, b"hello, world!");
@@ -527,4 +542,4 @@ mod test {
}
}
}
}
}

View File

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

View File

@@ -8,21 +8,30 @@ use std::{
use crossbeam_channel::Receiver;
use super::fetcher::{ImfsFetcher, FileType, ImfsEvent};
use super::fetcher::{FileType, ImfsEvent, ImfsFetcher};
pub struct NoopFetcher;
impl ImfsFetcher for NoopFetcher {
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>> {
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>> {
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<()> {
@@ -37,13 +46,11 @@ impl ImfsFetcher for NoopFetcher {
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> {
crossbeam_channel::never()
}
}
}

View File

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

View File

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

View File

@@ -15,4 +15,4 @@ macro_rules! impl_from {
}
)*
}
}
}

View File

@@ -1,4 +1,4 @@
#![recursion_limit="128"]
#![recursion_limit = "128"]
// Macros
#[macro_use]
@@ -16,4 +16,4 @@ mod serve_session;
mod session_id;
mod snapshot;
mod snapshot_middleware;
mod web;
mod web;

View File

@@ -1,9 +1,6 @@
use std::{
mem,
sync::{
RwLock,
Mutex,
},
sync::{Mutex, RwLock},
};
use futures::sync::oneshot;
@@ -13,7 +10,10 @@ struct Listener<T> {
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;
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.
#[derive(Default)]
pub struct MessageQueue<T> {
messages: RwLock<Vec<T>>,
message_listeners: Mutex<Vec<Listener<T>>>,
messages: RwLock<Vec<T>>,
message_listeners: Mutex<Vec<Listener<T>>>,
}
impl<T: Clone> MessageQueue<T> {
@@ -52,7 +52,7 @@ impl<T: Clone> MessageQueue<T> {
for listener in message_listeners.drain(..) {
match fire_listener_if_ready(&messages, listener) {
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>)>) {
let listener = {
let listener = Listener {
sender,
cursor,
};
let listener = Listener { sender, cursor };
let messages = self.messages.read().unwrap();
match fire_listener_if_ready(&messages, listener) {
Ok(_) => return,
Err(listener) => listener
Err(listener) => listener,
}
};
@@ -96,4 +93,4 @@ impl<T: Clone> MessageQueue<T> {
(current_cursor, messages[(cursor as usize)..].to_vec())
}
}
}

View File

@@ -1,10 +1,10 @@
use std::{
path::{self, Path, PathBuf},
collections::{HashMap, HashSet},
path::{self, Path, PathBuf},
};
use serde::Serialize;
use log::warn;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct PathMapNode<T> {
@@ -51,7 +51,9 @@ impl<T> PathMap<T> {
}
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 {
@@ -76,10 +78,7 @@ impl<T> PathMap<T> {
self.orphan_paths.remove(child);
}
self.nodes.insert(path, PathMapNode {
value,
children,
});
self.nodes.insert(path, PathMapNode { value, children });
}
/// 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() {
to_visit.push(child);
}
},
}
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
/// directory being removed, in which case we should process that
/// 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 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");
let mut current_path = start_path;
@@ -141,7 +148,7 @@ impl<T> PathMap<T> {
} else {
return current_path;
}
},
}
_ => unreachable!(),
}
}
@@ -219,9 +226,7 @@ mod test {
map.insert("/foo", 6);
assert_eq!(map.remove("/foo"), vec![
(PathBuf::from("/foo"), 6),
]);
assert_eq!(map.remove("/foo"), vec![(PathBuf::from("/foo"), 6),]);
assert_eq!(map.get("/foo"), None);
}
@@ -233,10 +238,10 @@ mod test {
map.insert("/foo", 6);
map.insert("/foo/bar", 12);
assert_eq!(map.remove("/foo"), vec![
(PathBuf::from("/foo"), 6),
(PathBuf::from("/foo/bar"), 12),
]);
assert_eq!(
map.remove("/foo"),
vec![(PathBuf::from("/foo"), 6), (PathBuf::from("/foo/bar"), 12),]
);
assert_eq!(map.get("/foo"), None);
assert_eq!(map.get("/foo/bar"), None);
@@ -250,11 +255,14 @@ mod test {
map.insert("/foo/bar", 12);
map.insert("/foo/bar/baz", 18);
assert_eq!(map.remove("/foo"), vec![
(PathBuf::from("/foo"), 6),
(PathBuf::from("/foo/bar"), 12),
(PathBuf::from("/foo/bar/baz"), 18),
]);
assert_eq!(
map.remove("/foo"),
vec![
(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/bar"), None);
@@ -268,11 +276,9 @@ mod test {
map.insert("/foo", 6);
map.insert("/foo/bar/baz", 12);
assert_eq!(map.remove("/foo"), vec![
(PathBuf::from("/foo"), 6),
]);
assert_eq!(map.remove("/foo"), vec![(PathBuf::from("/foo"), 6),]);
assert_eq!(map.get("/foo"), None);
assert_eq!(map.get("/foo/bar/baz"), Some(&12));
}
}
}

View File

@@ -33,22 +33,27 @@ use std::path::{Component, Path};
use serde::Serializer;
pub fn serialize_option<S, T>(maybe_path: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,
T: AsRef<Path>,
where
S: Serializer,
T: AsRef<Path>,
{
match maybe_path {
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>
where S: Serializer,
T: AsRef<Path>,
where
S: Serializer,
T: AsRef<Path>,
{
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();
@@ -66,4 +71,4 @@ pub fn serialize<S, T>(path: T, serializer: S) -> Result<S::Ok, S::Error>
}
serializer.serialize_str(&output)
}
}

View File

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

View File

@@ -1,9 +1,6 @@
use std::collections::HashSet;
use crate::{
project::Project,
session_id::SessionId,
};
use crate::{project::Project, session_id::SessionId};
/// Contains all of the state for a Rojo serve session.
pub struct ServeSession {
@@ -30,4 +27,4 @@ impl ServeSession {
.as_ref()
.and_then(|project| project.serve_place_ids.as_ref())
}
}
}

View File

@@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -8,4 +8,4 @@ impl SessionId {
pub fn new() -> SessionId {
SessionId(Uuid::new_v4())
}
}
}

View File

@@ -1,11 +1,8 @@
//! Defines the structure of an instance snapshot.
use std::{
borrow::Cow,
collections::HashMap,
};
use std::{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
/// 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 properties: HashMap<String, RbxValue>,
pub children: Vec<InstanceSnapshot<'source>>,
// TODO: Snapshot source, like a file or a project node?
}
impl<'source> InstanceSnapshot<'source> {
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)
.collect();
@@ -42,10 +40,12 @@ impl<'source> InstanceSnapshot<'source> {
}
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");
let children = instance.get_children_ids()
let children = instance
.get_children_ids()
.iter()
.cloned()
.map(|id| InstanceSnapshot::from_tree(tree, id))
@@ -59,4 +59,4 @@ impl<'source> InstanceSnapshot<'source> {
children,
}
}
}
}

View File

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

View File

@@ -2,7 +2,7 @@
use std::collections::HashMap;
use rbx_dom_weak::{RbxValue, RbxId};
use rbx_dom_weak::{RbxId, RbxValue};
use super::InstanceSnapshot;
@@ -41,4 +41,4 @@ pub struct PatchUpdateInstance {
/// Contains all changed properties. If a property is assigned to `None`,
/// then that property has been removed.
pub changed_properties: HashMap<String, Option<RbxValue>>,
}
}

View File

@@ -2,17 +2,14 @@
use std::collections::HashMap;
use rbx_dom_weak::{RbxTree, RbxValue, RbxId, RbxInstanceProperties};
use rbx_dom_weak::{RbxId, RbxInstanceProperties, RbxTree, RbxValue};
use super::{
patch::{PatchSet, PatchUpdateInstance},
InstanceSnapshot,
};
pub fn apply_patch_set(
tree: &mut RbxTree,
patch_set: &PatchSet,
) {
pub fn apply_patch_set(tree: &mut RbxTree, patch_set: &PatchSet) {
let mut context = PatchApplyContext::default();
for removed_id in &patch_set.removed_instances {
@@ -47,13 +44,16 @@ struct PatchApplyContext {
/// then apply properties all at once at the end.
fn apply_deferred_properties(context: PatchApplyContext, tree: &mut RbxTree) {
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");
for property_value in properties.values_mut() {
if let RbxValue::Ref { value: Some(id) } = property_value {
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);
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 {
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
@@ -95,7 +97,8 @@ fn apply_update_child(
tree: &mut RbxTree,
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");
if let Some(name) = &patch.changed_name {
@@ -114,9 +117,12 @@ fn apply_update_child(
Some(RbxValue::Ref { value: Some(id) }) => {
let new_id = context.snapshot_id_to_instance_id.get(id).unwrap_or(id);
instance.properties.insert(key.clone(), RbxValue::Ref {
value: Some(*new_id),
});
instance.properties.insert(
key.clone(),
RbxValue::Ref {
value: Some(*new_id),
},
);
}
Some(value) => {
instance.properties.insert(key.clone(), value.clone());
@@ -132,10 +138,7 @@ fn apply_update_child(
mod test {
use super::*;
use std::{
borrow::Cow,
collections::HashMap,
};
use std::{borrow::Cow, collections::HashMap};
use maplit::hashmap;
use rbx_dom_weak::RbxValue;
@@ -165,12 +168,10 @@ mod test {
};
let patch_set = PatchSet {
added_instances: vec![
PatchAddInstance {
parent_id: root_id,
instance: snapshot.clone(),
}
],
added_instances: vec![PatchAddInstance {
parent_id: root_id,
instance: snapshot.clone(),
}],
..Default::default()
};
@@ -236,4 +237,4 @@ mod test {
assert_eq!(root_instance.class_name, "NewClassName");
assert_eq!(root_instance.properties, expected_properties);
}
}
}

View File

@@ -3,11 +3,11 @@
use std::collections::{HashMap, HashSet};
use rbx_dom_weak::{RbxTree, RbxValue, RbxId, RbxInstance};
use rbx_dom_weak::{RbxId, RbxInstance, RbxTree, RbxValue};
use super::{
patch::{PatchAddInstance, PatchSet, PatchUpdateInstance},
InstanceSnapshot,
patch::{PatchSet, PatchAddInstance, PatchUpdateInstance},
};
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() {
if let Some(RbxValue::Ref { value: Some(id) }) = property_value {
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() {
if let RbxValue::Ref { value: Some(id) } = property_value {
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);
}
let instance = tree.get_instance(id)
let instance = tree
.get_instance(id)
.expect("Instance did not exist in tree");
compute_property_patches(snapshot, instance, patch_set);
@@ -145,7 +150,8 @@ fn compute_children_patches<'a>(
id: RbxId,
patch_set: &mut PatchSet<'a>,
) {
let instance = tree.get_instance(id)
let instance = tree
.get_instance(id)
.expect("Instance did not exist in tree");
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()];
for snapshot_child in snapshot.children.iter() {
let matching_instance = instance_children
.iter()
.enumerate()
.find(|(instance_index, instance_child_id)| {
if paired_instances[*instance_index] {
return false;
}
let matching_instance =
instance_children
.iter()
.enumerate()
.find(|(instance_index, instance_child_id)| {
if paired_instances[*instance_index] {
return false;
}
let instance_child = tree.get_instance(**instance_child_id)
.expect("Instance did not exist in tree");
let instance_child = tree
.get_instance(**instance_child_id)
.expect("Instance did not exist in tree");
if snapshot_child.name == instance_child.name &&
instance_child.class_name == instance_child.class_name
{
paired_instances[*instance_index] = true;
return true;
}
if snapshot_child.name == instance_child.name
&& instance_child.class_name == instance_child.class_name
{
paired_instances[*instance_index] = true;
return true;
}
false
});
false
});
match matching_instance {
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 => {
patch_set.added_instances.push(PatchAddInstance {
@@ -238,18 +252,16 @@ mod test {
let patch_set = compute_patch_set(&snapshot, &tree, root_id);
let expected_patch_set = PatchSet {
updated_instances: vec![
PatchUpdateInstance {
id: root_id,
changed_name: None,
changed_class_name: None,
changed_properties: hashmap! {
"Self".to_owned() => Some(RbxValue::Ref {
value: Some(root_id),
}),
},
updated_instances: vec![PatchUpdateInstance {
id: root_id,
changed_name: None,
changed_class_name: None,
changed_properties: hashmap! {
"Self".to_owned() => Some(RbxValue::Ref {
value: Some(root_id),
}),
},
],
}],
added_instances: Vec::new(),
removed_instances: Vec::new(),
};
@@ -274,20 +286,18 @@ mod test {
let snapshot_id = RbxId::new();
let snapshot = InstanceSnapshot {
snapshot_id: Some(snapshot_id),
children: vec![
InstanceSnapshot {
properties: hashmap! {
"Self".to_owned() => RbxValue::Ref {
value: Some(snapshot_id),
},
children: vec![InstanceSnapshot {
properties: hashmap! {
"Self".to_owned() => RbxValue::Ref {
value: Some(snapshot_id),
},
},
snapshot_id: None,
name: Cow::Borrowed("child"),
class_name: Cow::Borrowed("child"),
children: Vec::new(),
}
],
snapshot_id: None,
name: Cow::Borrowed("child"),
class_name: Cow::Borrowed("child"),
children: Vec::new(),
}],
properties: HashMap::new(),
name: Cow::Borrowed("foo"),
@@ -297,26 +307,24 @@ mod test {
let patch_set = compute_patch_set(&snapshot, &tree, root_id);
let expected_patch_set = PatchSet {
added_instances: vec![
PatchAddInstance {
parent_id: root_id,
instance: InstanceSnapshot {
snapshot_id: None,
properties: hashmap! {
"Self".to_owned() => RbxValue::Ref {
value: Some(root_id),
},
added_instances: vec![PatchAddInstance {
parent_id: root_id,
instance: InstanceSnapshot {
snapshot_id: None,
properties: hashmap! {
"Self".to_owned() => RbxValue::Ref {
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(),
removed_instances: Vec::new(),
};
assert_eq!(patch_set, expected_patch_set);
}
}
}

View File

@@ -4,4 +4,4 @@ pub struct InstanceSnapshotContext {
pub plugin_context: Option<()>,
}
pub struct ImfsSnapshotContext;
pub struct ImfsSnapshotContext;

View File

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

View File

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

View File

@@ -1,12 +1,6 @@
use std::{
fmt,
error::Error,
path::PathBuf,
};
use std::{error::Error, fmt, path::PathBuf};
use crate::{
snapshot::InstanceSnapshot,
};
use crate::snapshot::InstanceSnapshot;
pub type SnapshotResult<'a> = Result<Option<InstanceSnapshot<'a>>, SnapshotError>;
@@ -43,9 +37,7 @@ impl SnapshotError {
path: impl Into<PathBuf>,
) -> SnapshotError {
SnapshotError {
detail: SnapshotErrorDetail::FileContentsBadUnicode {
inner,
},
detail: SnapshotErrorDetail::FileContentsBadUnicode { inner },
path: Some(path.into()),
}
}
@@ -70,9 +62,7 @@ impl fmt::Display for SnapshotError {
pub enum SnapshotErrorDetail {
FileDidNotExist,
FileNameBadUnicode,
FileContentsBadUnicode {
inner: std::str::Utf8Error,
},
FileContentsBadUnicode { inner: std::str::Utf8Error },
}
impl SnapshotErrorDetail {
@@ -81,7 +71,7 @@ impl SnapshotErrorDetail {
match self {
FileContentsBadUnicode { inner } => Some(inner),
_ => None
_ => None,
}
}
}
@@ -93,7 +83,9 @@ impl fmt::Display for SnapshotErrorDetail {
match self {
FileDidNotExist => write!(formatter, "file did not exist"),
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::{
borrow::Cow,
collections::HashMap,
};
use std::{borrow::Cow, collections::HashMap};
use rbx_dom_weak::{RbxId, RbxTree, UnresolvedRbxValue};
use rbx_reflection::try_resolve_value;
use rbx_dom_weak::{RbxTree, RbxId, UnresolvedRbxValue};
use serde::{Deserialize};
use serde::Deserialize;
use crate::{
imfs::new::{Imfs, ImfsFetcher, ImfsEntry},
imfs::new::{Imfs, ImfsEntry, ImfsFetcher},
snapshot::InstanceSnapshot,
};
use super::{
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
use super::middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware};
pub struct SnapshotJsonModel;
@@ -27,22 +22,30 @@ impl SnapshotMiddleware for SnapshotJsonModel {
return Ok(None);
}
let file_name = entry.path()
.file_name().unwrap().to_string_lossy();
let file_name = entry.path().file_name().unwrap().to_string_lossy();
let instance_name = match match_trailing(&file_name, ".model.json") {
Some(name) => name.to_owned(),
None => return Ok(None),
};
let instance: JsonModel = serde_json::from_slice(entry.contents(imfs)?)
.expect("TODO: Handle serde_json errors");
let instance: JsonModel =
serde_json::from_slice(entry.contents(imfs)?).expect("TODO: Handle serde_json errors");
if let Some(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!("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 from JSON model did not match its file 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.");
}
}
@@ -52,10 +55,7 @@ impl SnapshotMiddleware for SnapshotJsonModel {
Ok(Some(snapshot))
}
fn from_instance(
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
unimplemented!("Snapshotting models");
}
}
@@ -103,14 +103,17 @@ impl JsonModelCore {
fn into_snapshot(self, name: String) -> InstanceSnapshot<'static> {
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))
.collect();
let properties = self.properties.into_iter()
let properties = self
.properties
.into_iter()
.map(|(key, value)| {
try_resolve_value(&class_name, &key, &value)
.map(|resolved| (key, resolved))
try_resolve_value(&class_name, &key, &value).map(|resolved| (key, resolved))
})
.collect::<Result<HashMap<_, _>, _>>()
.expect("TODO: Handle rbx_reflection errors");
@@ -137,7 +140,8 @@ mod test {
#[test]
fn model_from_imfs() {
let mut imfs = Imfs::new(NoopFetcher);
let file = ImfsSnapshot::file(r#"
let file = ImfsSnapshot::file(
r#"
{
"Name": "children",
"ClassName": "IntValue",
@@ -151,31 +155,35 @@ mod test {
}
]
}
"#);
"#,
);
imfs.load_from_snapshot("/foo.model.json", file);
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 {
snapshot_id: None,
name: Cow::Borrowed("foo"),
class_name: Cow::Borrowed("IntValue"),
properties: hashmap! {
"Value".to_owned() => RbxValue::Int32 {
value: 5,
assert_eq!(
instance_snapshot,
InstanceSnapshot {
snapshot_id: None,
name: Cow::Borrowed("foo"),
class_name: Cow::Borrowed("IntValue"),
properties: hashmap! {
"Value".to_owned() => RbxValue::Int32 {
value: 5,
},
},
},
children: vec![
InstanceSnapshot {
children: vec![InstanceSnapshot {
snapshot_id: None,
name: Cow::Borrowed("The Child"),
class_name: Cow::Borrowed("StringValue"),
properties: HashMap::new(),
children: Vec::new(),
},
],
});
},],
}
);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,20 @@
use std::{
borrow::Cow,
collections::HashMap,
};
use std::{borrow::Cow, collections::HashMap};
use rbx_dom_weak::{RbxTree, RbxId};
use rbx_dom_weak::{RbxId, RbxTree};
use rbx_reflection::try_resolve_value;
use crate::{
project::{Project, ProjectNode},
imfs::{
new::{Imfs, ImfsEntry, ImfsFetcher},
FsErrorKind,
new::{Imfs, ImfsFetcher, ImfsEntry},
},
project::{Project, ProjectNode},
snapshot::InstanceSnapshot,
};
use super::{
middleware::{SnapshotFileResult, SnapshotInstanceResult, SnapshotMiddleware},
snapshot_from_imfs,
middleware::{SnapshotMiddleware, SnapshotInstanceResult, SnapshotFileResult},
};
pub struct SnapshotProject;
@@ -38,7 +35,7 @@ impl SnapshotMiddleware for SnapshotProject {
}
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())
@@ -47,10 +44,7 @@ impl SnapshotMiddleware for SnapshotProject {
snapshot_project_node(&project.name, &project.tree, imfs)
}
fn from_instance(
_tree: &RbxTree,
_id: RbxId,
) -> SnapshotFileResult {
fn from_instance(_tree: &RbxTree, _id: RbxId) -> SnapshotFileResult {
// TODO: Supporting turning instances into projects
None
}
@@ -61,10 +55,14 @@ fn snapshot_project_node<F: ImfsFetcher>(
node: &ProjectNode,
imfs: &mut Imfs<F>,
) -> 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 mut class_name = node.class_name
let mut class_name = node
.class_name
.as_ref()
.map(|name| Cow::Owned(name.clone()));
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");
}
}
None => Some(snapshot.class_name)
None => Some(snapshot.class_name),
};
// Properties from the snapshot are pulled in unchanged, and
@@ -108,7 +106,9 @@ fn snapshot_project_node<F: ImfsFetcher>(
}
} else {
// 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 {
use super::*;
use rbx_dom_weak::RbxValue;
use maplit::hashmap;
use rbx_dom_weak::RbxValue;
use crate::imfs::new::{ImfsSnapshot, NoopFetcher};
@@ -236,11 +236,14 @@ mod test {
assert_eq!(instance_snapshot.name, "resolved-properties");
assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hello, world!".to_owned(),
},
});
assert_eq!(
instance_snapshot.properties,
hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hello, world!".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new());
}
@@ -272,11 +275,14 @@ mod test {
assert_eq!(instance_snapshot.name, "unresolved-properties");
assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hi!".to_owned(),
},
});
assert_eq!(
instance_snapshot.properties,
hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hi!".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new());
}
@@ -345,11 +351,14 @@ mod test {
assert_eq!(instance_snapshot.name, "path-project");
assert_eq!(instance_snapshot.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hello, world!".to_owned(),
},
});
assert_eq!(
instance_snapshot.properties,
hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Hello, world!".to_owned(),
},
}
);
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.class_name, "StringValue");
assert_eq!(instance_snapshot.properties, hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Changed".to_owned(),
},
});
assert_eq!(
instance_snapshot.properties,
hashmap! {
"Value".to_owned() => RbxValue::String {
value: "Changed".to_owned(),
},
}
);
assert_eq!(instance_snapshot.children, Vec::new());
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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