mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-22 13:45:16 +00:00
rustfmt the codebase
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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"
|
|
||||||
@@ -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);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()?;
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
@@ -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::*;
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
use std::{
|
use std::{fmt, io, path::PathBuf};
|
||||||
io,
|
|
||||||
fmt,
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use failure::Fail;
|
use failure::Fail;
|
||||||
|
|
||||||
|
|||||||
@@ -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!");
|
||||||
|
|||||||
@@ -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::*;
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#![recursion_limit="128"]
|
#![recursion_limit = "128"]
|
||||||
|
|
||||||
// Macros
|
// Macros
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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::*;
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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(),
|
||||||
},
|
},],
|
||||||
],
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,)* ) => {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")),
|
||||||
|
|||||||
Reference in New Issue
Block a user