mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-23 06:05:24 +00:00
Implement Syncback to support converting Roblox files to a Rojo project (#937)
This is a very large commit. Consider checking the linked PR for more information.
This commit is contained in:
128
src/syncback/file_names.rs
Normal file
128
src/syncback/file_names.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
//! Contains logic for generating new file names for Instances based on their
|
||||
//! middleware.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Context;
|
||||
use rbx_dom_weak::Instance;
|
||||
|
||||
use crate::{snapshot::InstanceWithMeta, snapshot_middleware::Middleware};
|
||||
|
||||
pub fn name_for_inst<'old>(
|
||||
middleware: Middleware,
|
||||
new_inst: &Instance,
|
||||
old_inst: Option<InstanceWithMeta<'old>>,
|
||||
) -> anyhow::Result<Cow<'old, str>> {
|
||||
if let Some(old_inst) = old_inst {
|
||||
if let Some(source) = old_inst.metadata().relevant_paths.first() {
|
||||
source
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(Cow::Borrowed)
|
||||
.context("sources on the file system should be valid unicode and not be stubs")
|
||||
} else {
|
||||
// This is technically not /always/ true, but we want to avoid
|
||||
// running syncback on anything that has no instigating source
|
||||
// anyway.
|
||||
anyhow::bail!(
|
||||
"members of 'old' trees should have an instigating source. Somehow, {} did not.",
|
||||
old_inst.name(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Ok(match middleware {
|
||||
Middleware::Dir
|
||||
| Middleware::CsvDir
|
||||
| Middleware::ServerScriptDir
|
||||
| Middleware::ClientScriptDir
|
||||
| Middleware::ModuleScriptDir => Cow::Owned(new_inst.name.clone()),
|
||||
_ => {
|
||||
let extension = extension_for_middleware(middleware);
|
||||
let name = &new_inst.name;
|
||||
validate_file_name(name).with_context(|| {
|
||||
format!("name '{name}' is not legal to write to the file system")
|
||||
})?;
|
||||
Cow::Owned(format!("{name}.{extension}"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the extension a provided piece of middleware is supposed to use.
|
||||
pub fn extension_for_middleware(middleware: Middleware) -> &'static str {
|
||||
match middleware {
|
||||
Middleware::Csv => "csv",
|
||||
Middleware::JsonModel => "model.json",
|
||||
Middleware::Json => "json",
|
||||
Middleware::ServerScript => "server.luau",
|
||||
Middleware::ClientScript => "client.luau",
|
||||
Middleware::ModuleScript => "luau",
|
||||
Middleware::PluginScript => "plugin.luau",
|
||||
Middleware::Project => "project.json",
|
||||
Middleware::Rbxm => "rbxm",
|
||||
Middleware::Rbxmx => "rbxmx",
|
||||
Middleware::Toml => "toml",
|
||||
Middleware::Text => "txt",
|
||||
Middleware::Yaml => "yml",
|
||||
|
||||
Middleware::LegacyServerScript
|
||||
| Middleware::LegacyClientScript
|
||||
| Middleware::RunContextServerScript
|
||||
| Middleware::RunContextClientScript => {
|
||||
todo!("syncback does not work on the middleware {middleware:?} yet")
|
||||
}
|
||||
// These are manually specified and not `_` to guard against future
|
||||
// middleware additions missing this function.
|
||||
Middleware::Ignore => unimplemented!("syncback does not work on Ignore middleware"),
|
||||
Middleware::Dir
|
||||
| Middleware::CsvDir
|
||||
| Middleware::ServerScriptDir
|
||||
| Middleware::ClientScriptDir
|
||||
| Middleware::ModuleScriptDir => {
|
||||
unimplemented!("directory middleware requires special treatment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of file names that are not valid on Windows.
|
||||
const INVALID_WINDOWS_NAMES: [&str; 22] = [
|
||||
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
|
||||
"COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
];
|
||||
|
||||
/// A list of all characters that are outright forbidden to be included
|
||||
/// in a file's name.
|
||||
const FORBIDDEN_CHARS: [char; 9] = ['<', '>', ':', '"', '/', '|', '?', '*', '\\'];
|
||||
|
||||
/// Validates a provided file name to ensure it's allowed on the file system. An
|
||||
/// error is returned if the name isn't allowed, indicating why.
|
||||
/// This takes into account rules for Windows, MacOS, and Linux.
|
||||
///
|
||||
/// In practice however, these broadly overlap so the only unexpected behavior
|
||||
/// is Windows, where there are 22 reserved names.
|
||||
pub fn validate_file_name<S: AsRef<str>>(name: S) -> anyhow::Result<()> {
|
||||
let str = name.as_ref();
|
||||
|
||||
if str.ends_with(' ') {
|
||||
anyhow::bail!("file names cannot end with a space")
|
||||
}
|
||||
if str.ends_with('.') {
|
||||
anyhow::bail!("file names cannot end with '.'")
|
||||
}
|
||||
|
||||
for char in str.chars() {
|
||||
if FORBIDDEN_CHARS.contains(&char) {
|
||||
anyhow::bail!("file names cannot contain <, >, :, \", /, |, ?, *, or \\")
|
||||
} else if char.is_control() {
|
||||
anyhow::bail!("file names cannot contain control characters")
|
||||
}
|
||||
}
|
||||
|
||||
for forbidden in INVALID_WINDOWS_NAMES {
|
||||
if str == forbidden {
|
||||
anyhow::bail!("files cannot be named {str}")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user