initial commit

This commit is contained in:
Rowan 2025-07-09 21:56:51 -04:00
commit 065844a66b
15 changed files with 9722 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2037
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

4
Cargo.toml Normal file
View file

@ -0,0 +1,4 @@
[workspace]
resolver = "3"
members = [ "core","gui"]

16
core/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "core"
version = "0.1.0"
edition = "2024"
[dependencies]
confique = { version = "0.3.0", features = ["toml"] }
derive_more = { version = "2.0.1", features = ["display", "from_str"] }
directories = "6.0.0"
serde = "1.0.219"
toml = "0.8.23"
unity_release_api = { git = "https://git.kitsu.cafe/rowan/unity-release-client.git", version = "0.1.0" }
osstr_traits = { path = "../../osstr_traits/crates/osstr_traits", version = "0.1.0" }
osstr_traits_derive = { path = "../../osstr_traits/crates/osstr_traits_derive", version = "0.1.0" }
#osstr_traits = { git = "https://git.kitsu.cafe/rowan/osstr-traits.git", version = "0.1.0" }
#osstr_traits_derive = { git = "https://git.kitsu.cafe/rowan/osstr-traits.git", version = "0.1.0" }

6355
core/blah.rs Normal file

File diff suppressed because it is too large Load diff

170
core/src/collection.rs Normal file
View file

@ -0,0 +1,170 @@
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
fmt::Display,
ops::{Deref, DerefMut},
path::PathBuf,
};
use derive_more::Display;
use osstr_traits::{OsDisplay, ToOsString};
type OsStrDisplay<'a> = std::ffi::os_str::Display<'a>;
type PathDisplay<'a> = std::path::Display<'a>;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DelimitedVec<T, const D: char = ' '>(Vec<T>);
impl<T: OsDisplay, const D: char> OsDisplay for DelimitedVec<T, D> {
fn fmt_os(&self, f: &mut osstr_traits::OsStringFormatter) -> std::fmt::Result {
let mut first = true;
for item in &self.0 {
if !first {
f.write_os_str(&D.to_os_string())?;
}
item.fmt_os(f)?;
first = false;
}
Ok(())
}
}
impl<T: Display, const D: char> Display for DelimitedVec<T, D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for item in &self.0 {
if !first {
write!(f, "{D}")?;
}
write!(f, "{item}")?;
first = false;
}
Ok(())
}
}
impl<A, const D: char> FromIterator<A> for DelimitedVec<A, D> {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<'a, const D: char> From<&'a Vec<PathBuf>> for DelimitedVec<PathDisplay<'a>, D> {
fn from(value: &'a Vec<PathBuf>) -> Self {
let iter = value.iter().map(|s| s.display());
Self::from_iter(iter)
}
}
impl<'a, const D: char> From<&'a Vec<OsString>> for DelimitedVec<OsStrDisplay<'a>, D> {
fn from(value: &'a Vec<OsString>) -> Self {
let iter = value.iter().map(|s| s.display());
Self::from_iter(iter)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CommandArgs<T>(pub Vec<T>);
impl<T: OsDisplay> OsDisplay for CommandArgs<T> {
fn fmt_os(&self, f: &mut osstr_traits::OsStringFormatter) -> std::fmt::Result {
let mut first = true;
for item in &self.0 {
if !first {
f.write_os_str(OsStr::new(" "))?;
}
item.fmt_os(f)?;
first = false;
}
Ok(())
}
}
impl<T: Display> Display for CommandArgs<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for item in &self.0 {
if !first {
write!(f, " ")?;
}
write!(f, "{item}")?;
first = false;
}
Ok(())
}
}
impl<T> Default for CommandArgs<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T> Deref for CommandArgs<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for CommandArgs<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<A> FromIterator<A> for CommandArgs<A> {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<T> IntoIterator for CommandArgs<T> {
type Item = T;
type IntoIter = <Vec<T> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[derive(Clone, Debug, Display)]
#[display("{} {}", command.display(), args)]
pub struct Command<'a, T> {
command: Cow<'a, OsStr>,
args: CommandArgs<T>,
}
impl<'a, T> Command<'a, T> {
pub fn new(command: impl Into<Cow<'a, OsStr>>) -> Self {
Self {
command: command.into(),
args: CommandArgs::default(),
}
}
pub fn arg(&mut self, arg: T) {
self.args.push(arg);
}
}
impl<'a, T: AsRef<OsStr>> Command<'a, T> {
pub fn into_os_command(self) -> std::process::Command {
let mut cmd = std::process::Command::new(self.command);
cmd.args(self.args);
cmd
}
}

108
core/src/config.rs Normal file
View file

@ -0,0 +1,108 @@
use std::{
borrow::Cow,
env,
path::{Path, PathBuf},
};
use confique::Config;
use directories::ProjectDirs;
use serde::{Deserialize, Deserializer, Serialize};
use crate::error::{Error, ShellExpansionError};
trait Fallback: Config {
fn fallback() -> <Self as Config>::Partial;
}
fn project_dir() -> Option<ProjectDirs> {
ProjectDirs::from("cafe", "kitsu", "TormentNexus")
}
fn expand_tilde<'a>(path: impl Into<Cow<'a, Path>>) -> Result<Cow<'a, Path>, ShellExpansionError> {
let path = path.into();
let into_err = || ShellExpansionError::new("~", path.to_str());
if !path.starts_with("~") {
Ok(path)
} else if path == Path::new("~") {
env::home_dir().map(Cow::Owned).ok_or_else(into_err)
} else {
let mut home_dir = env::home_dir().ok_or_else(into_err)?;
if home_dir == Path::new("/") {
let stripped = path.strip_prefix("~").map_err(|_e| into_err())?;
Ok(Cow::Owned(stripped.into()))
} else {
home_dir.push(path.strip_prefix("~/").map_err(|_e| into_err())?);
Ok(Cow::Owned(home_dir))
}
}
}
fn deserialize_path<'de, D: Deserializer<'de>>(de: D) -> Result<PathBuf, D::Error> {
let path = PathBuf::deserialize(de)?;
if let Ok(expanded_path) = expand_tilde(&path) {
Ok(expanded_path.into())
} else {
Ok(path)
}
}
#[derive(Config, Debug, Serialize, Deserialize)]
pub struct EditorCfg {
#[config(deserialize_with = deserialize_path, env = "EDITOR_PATH")]
pub path: PathBuf,
}
impl Fallback for EditorCfg {
fn fallback() -> <Self as Config>::Partial {
Self::Partial {
path: project_dir().map(|d| d.data_local_dir().join("editors/")),
}
}
}
#[derive(Debug, Config, Serialize, Deserialize)]
pub struct ProjectCfg {
#[config(deserialize_with = deserialize_path, env = "PROJECT_MANIFEST_PATH")]
pub manifest_path: PathBuf,
}
impl Fallback for ProjectCfg {
fn fallback() -> <Self as Config>::Partial {
Self::Partial {
manifest_path: project_dir().map(|d| d.data_local_dir().join("manifest.toml")),
}
}
}
#[derive(Debug, Config, Serialize, Deserialize)]
pub struct Cfg {
#[config(nested)]
pub editor: EditorCfg,
#[config(nested)]
pub project: ProjectCfg,
}
impl Cfg {
pub fn from_env() -> Result<Cfg, Error> {
let mut builder = Cfg::builder().env();
if let Some(dir) = project_dir() {
builder = builder.file(dir.config_dir().join("config.toml"));
}
builder
.preloaded(Self::fallback())
.load()
.map_err(Into::into)
}
}
impl Fallback for Cfg {
fn fallback() -> <Self as Config>::Partial {
Self::Partial {
editor: EditorCfg::fallback(),
project: ProjectCfg::fallback(),
}
}
}

1
core/src/editor.rs Normal file
View file

@ -0,0 +1 @@
pub struct Editor {}

795
core/src/editor_command.rs Normal file
View file

@ -0,0 +1,795 @@
use std::{
ffi::OsString,
net::IpAddr,
ops::{Deref, DerefMut},
path::PathBuf,
time::Duration,
};
use derive_more::Display;
use osstr_traits_derive::OsDisplay;
use serde::{Deserialize, Serialize};
use crate::collection::{CommandArgs, DelimitedVec};
type OsStrDisplay<'a> = std::ffi::os_str::Display<'a>;
type PathDisplay<'a> = std::path::Display<'a>;
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum ConsistencyCheckSourceMode {
#[display("local")]
Local,
#[display("cacheserver")]
CacheServer,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("{0} {1}", DelimitedVec::<PathDisplay>::from(exports), path.display())]
#[os_display("{exports} {path}", exports = DelimitedVec::<&PathBuf, ' '>::from_iter(exports), path = path)]
pub struct ExportPackage {
path: PathBuf,
exports: Vec<PathBuf>,
}
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum PlatformTextureFormat {
#[display("dxt")]
Dxt,
#[display("pvrtc")]
#[deprecated(note = "PVRTC format is deprecated. Use ASTC or ETC format instead.")]
Pvrtc,
#[display("atc")]
Atc,
#[display("etc")]
Etc,
#[display("etc2")]
Etc2,
#[display("astc")]
Astc,
}
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum TextureCompression {
#[default]
NoOverride,
ForceUncompressed,
ForceFastCompressor,
ForceNoCrunchCompression,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum SharedLogLevel {
#[display("debug")]
Debug,
#[display("info")]
Info,
#[display("notice")]
Notice,
#[display("fatal")]
Fatal,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
pub enum PerforceArgument {
#[display("vcPerforceHost {0}", _0.display())]
#[os_display("vcPerforceHost {_0}")]
Host(OsString),
#[display("vcSharedLogLevel {_0}")]
#[os_display("vcSharedLogLevel {_0}")]
SharedLogLevel(SharedLogLevel),
#[display("vcPerforceServer {0}", _0.display())]
#[os_display("vcPerforceServer {_0}")]
Server(OsString),
#[display("vcPerforceWorkspace {0}", _0.display())]
#[os_display("vcPerforceWorkspace {_0}")]
Workspace(OsString),
#[display("vcPerforceUsername {0}", _0.display())]
#[os_display("vcPerforceUsername {_0}")]
Username(OsString),
#[display("vcPerforcePassword {0}", _0.display())]
#[os_display("vcPerforcePassword {_0}")]
Password(OsString),
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum VcsMode {
#[display("\"Visible Meta Files\"")]
VisibleMetaFiles,
#[display("\"Hidden Meta Files\"")]
HiddenMetaFiles,
#[display("Perforce {_0}")]
#[os_display("Perforce {_0}")]
Perforce(CommandArgs<PerforceArgument>),
#[display("PlasticSCM")]
PlasticScm,
#[display("{0}", _0.display())]
#[os_display("{_0}")]
Other(OsString),
}
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum ConfigurationArgument {
#[display("createProject \"{0}\"", _0.display())]
#[os_display("-createProject \"{_0}\"")]
CreateProject(PathBuf),
#[display("consistencyCheck")]
ConsistencyCheck,
#[display("consistencyCheckSourceMode {_0}")]
ConsistencyCheckSourceMode(ConsistencyCheckSourceMode),
#[display("disable-assembly-updater {0}", DelimitedVec::<PathDisplay, ' '>::from(_0))]
#[os_display("-disable-assembly-updater {}", DelimitedVec::<&PathBuf, ' '>::from_iter(_0))]
DisableAssemblyUpdater(Vec<PathBuf>),
#[display("disable-gpu-skinning")]
DisableGpuSkinning,
#[display("disable-playback-engines {0}", DelimitedVec::<OsStrDisplay, ' '>::from(_0))]
#[os_display("-disable-playback-engines {}", DelimitedVec::<&std::ffi::OsString, ' '>::from_iter(_0))]
DisablePlaybackEngines(Vec<OsString>),
#[display("executeMethod {0}", _0.display())]
#[os_display("-executeMethod {_0}")]
ExecMethod(OsString),
#[display("exportPackage {_0}")]
#[os_display("-exportPackage {_0}")]
ExportPackage(ExportPackage),
#[display("importPackage {0}", _0.display())]
#[os_display("-importPackage {_0}")]
ImportPackage(PathBuf),
#[display("job-worker-count {_0}")]
JobWorkerCount(usize),
#[display("gc-helper-count {_0}")]
GcHelperCount(usize),
#[display("logFile {0}", _0.display())]
#[os_display("-logFile {_0}")]
LogFile(PathBuf),
#[display("noUpm")]
NoUpm,
#[display("openfile {0}", _0.display())]
#[os_display("-openfile {_0}")]
OpenFile(PathBuf),
#[display("password {0}", _0.display())]
#[os_display("-password {_0}")]
Password(OsString),
#[display("projectPath {0}", _0.display())]
#[os_display("-projectPath {_0}")]
ProjectPath(PathBuf),
#[display("quit")]
Quit,
#[display("releaseCodeOptimization")]
ReleaseCodeOptimization,
#[display("setDefaultPlatformTextureFormat {_0}")]
SetDefaultPlatformTextureFormat(PlatformTextureFormat),
#[display("overrideMaxTextureSize {_0}")]
OverrideMaxTextureSize(usize),
#[display("overrideTextureCompression {_0}")]
OverrideTextureCompression(TextureCompression),
#[display("silent-crashes")]
SilentCrashes,
#[display("upmLogFile {0}", _0.display())]
#[os_display("-upmLogFile {_0}")]
UpmLogFile(PathBuf),
#[display("username {0}", _0.display())]
#[os_display("-username {_0}")]
Username(OsString),
#[display("vcsMode {_0}")]
VcsMode(VcsMode),
#[display("version")]
Version,
#[display("timestamps")]
Timestamps,
}
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum BatchModeArgument {
#[display("accent-apiupdate")]
AcceptApiUpdate,
#[display("batchmode")]
BatchMode,
#[display("ignorecompilererrors")]
IgnoreCompilerErrors,
#[display("nographics")]
NoGraphics,
}
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum BuildTarget {
#[display("win64")]
Win64,
#[display("win")]
Win,
#[display("osxuniversal")]
OsxUniversal,
#[display("linux64")]
Linux64,
#[display("android")]
Android,
#[display("ios")]
IOs,
#[display("webgl")]
WebGl,
#[display("tvos")]
TvOs,
#[display("windowsstoreapps")]
WindowsStoreApps,
#[display("cloudrendering")]
CloudRendering,
#[display("visionos")]
VisionOs,
}
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum BuildSubtarget {
Player,
Server,
}
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
pub enum BuildArgument {
#[display("activeBuildProfile \"{0}\"", _0.display())]
#[os_display("activeBuildProfile \"{_0}\"")]
ActiveBuildProfile(PathBuf),
#[display("build \"{0}\"", _0.display())]
#[os_display("build \"{_0}\"")]
Build(PathBuf),
#[display("buildLinux64Player \"{0}\"", _0.display())]
#[os_display("buildLinux64Player \"{_0}\"")]
BuildLinux64Player(PathBuf),
#[display("buildLinuxHeadlessSimulation \"{0}\"", _0.display())]
#[os_display("buildLinuxHeadlessSimulation \"{_0}\"")]
BuildLinuxHeadlessSimulation(PathBuf),
#[display("buildOSXUniversalPlayer \"{0}\"", _0.display())]
#[os_display("buildOSXUniversalPlayer \"{_0}\"")]
BuildOsxUniversalPlayer(PathBuf),
#[display("buildTarget \"{_0}\"")]
BuildTarget(BuildTarget),
#[display("standaloneBuildSubtarget \"{_0}\"")]
#[os_display("standaloneBuildSubtarget \"{_0}\"")]
StandaloneBuildSubtarget(BuildSubtarget),
#[display("buildWindowsPlayer \"{0}\"", _0.display())]
#[os_display("buildWindowsPlayer \"{_0}\"")]
BuildWindowsPlayer(PathBuf),
#[display("buildWindows64Player \"{0}\"", _0.display())]
#[os_display("buildWindows64Player \"{_0}\"")]
BuildWindows64Player(PathBuf),
}
#[derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum CacheServerArgument {
#[display("EnableCacheServer")]
Enable,
#[display("cacheServerEndpoint {_0}:{_1}")]
Endpoint(IpAddr, u16),
#[display("cacheServerNamespacePrefix \"{0}\"", _0.display())]
#[os_display("cacheServerNamespacePrefix \"{_0}\"")]
NamespacePrefix(OsString),
#[display("cacheServerEnableDownload {_0}")]
EnableDownload(bool),
#[display("cacheServerEnableUpload {_0}")]
EnableUpload(bool),
#[display("cacheServerWaitForConnection {0}", _0.as_millis())]
WaitForConnection(Duration),
#[display("cacheServerWaitForUploadCompletion")]
WaitForUploadCompletion,
#[display("cacheServerDownloadBatchSize {_0}")]
DownloadBatchSize(usize),
#[display("cacheServerUploadAllRevisions")]
UploadAllRevisions,
#[display("cacheServerUploadExistingShaderCache")]
UploadExistingShaderCache,
}
#[derive(Debug, Default, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[os_display(from_display)]
pub enum StackTraceLogType {
None,
#[display("\"Script Only\"")]
ScriptOnly,
#[default]
Full,
}
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum DebugArgument {
#[display("disableManagedDebugger")]
DisableManagedDebugger,
#[display("diag-debug-shader-compiler")]
DiagDebugShaderCompiler,
#[display("debugCodeOptimization")]
DebugCodeOptimization,
#[display("enableCodeCoverage")]
EnableCodeCoverage,
#[display("force-d3d12-debug")]
ForceD3d12Debug,
#[display("force-d3d12-debug-gbv")]
ForceD3d12DebugGbv,
#[display("force-vulkan-layers")]
ForceVulkanLayers,
#[display("StackTraceLogType {_0}")]
#[os_display("StackTraceLogType {_0}")]
StackTraceLogType(StackTraceLogType),
#[display("log-memory-performance-stats")]
LogMemoryPerformanceStats,
#[display("wait-for-managed-debugger")]
WaitForManagedDebugger,
#[display("wait-for-native-debugger")]
WaitForNativeDebugger,
}
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum GraphicsApiArgument {
#[display("force-clamped")]
ForceClamped,
#[display("force-d3d11")]
ForceD3d11,
#[display("force-d3d12")]
ForceD3d12,
#[display("force-device-index")]
ForceDeviceIndex,
#[display("force-glcore")]
ForceGlCore,
#[display("force-glcoreXY")]
ForceGlCoreXy,
#[display("force-gles")]
ForceGlEs,
#[display("force-glesXY")]
ForceGlEsXy,
#[display("force-opengl")]
ForceOpenGl,
#[display("force-vulkan")]
ForceVulkan,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum LicenseArgument {
#[display("createManualActivationFile")]
CreateManualActivationFile,
#[display("manualLicenseFile {0}", _0.display())]
#[os_display("manualLicenseFile {_0}")]
ManualLicenseFile(PathBuf),
#[display("returnlicense")]
ReturnLicense,
#[display("serial {0}", _0.display())]
#[os_display("serial {_0}")]
Serial(OsString),
}
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum MetalArgument {
#[display("force-low-power-device")]
ForceLowPowerDevice,
#[display("force-metal")]
ForceMetal,
#[display("enable-metal-capture")]
EnableMetalCapture,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum ProfilerArgument {
#[display("deepprofiling")]
DeepProfiling,
#[display("profiler-enable")]
Enable,
#[display("profiler-log-file {0}", _0.display())]
#[os_display("profiler-log-file {_0}")]
LogFile(PathBuf),
#[display("profiler-capture-frame-count {_0}")]
CaptureFrameCount(usize),
#[display("profiler-maxusedmemory {_0}")]
MaxUsedMemory(usize),
}
#[derive(Clone, Debug, Display, PartialEq, Eq, PartialOrd, Ord, OsDisplay)]
#[display("-{_variant}")]
#[os_display(from_display)]
pub enum EditorSpecialArgument {
#[display("enableIncompatibleAssetDowngrade")]
EnableIncompatibleAssetDowngrade,
#[display("giCustomCacheLocation {0}", _0.display())]
#[os_display("giCustomCacheLocation {_0}")]
GiCustomCacheLocation(PathBuf),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum UnityArgument {
Configuration(ConfigurationArgument),
BatchMode(BatchModeArgument),
Build(BuildArgument),
CacheServer(CacheServerArgument),
Debug(DebugArgument),
GraphicsApi(GraphicsApiArgument),
License(LicenseArgument),
Metal(MetalArgument),
Profiler(ProfilerArgument),
Editor(EditorSpecialArgument),
Extra(OsString),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct UnityArguments(Vec<UnityArgument>);
impl Deref for UnityArguments {
type Target = Vec<UnityArgument>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for UnityArguments {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Default for UnityArguments {
fn default() -> Self {
Self(Vec::new())
}
}
macro_rules! unity_args {
() => {
UnityArguments::default()
};
($arg:expr $(, $rest:expr)* $(,)?) => {
{
let mut args = Vec::new();
$(
#[allow(unreachable_patterns)]
match $rest {
arg @ ConfigArguments::CreateProject(_) |
arg @ ConfigArguments::ConsistencyCheck |
arg @ ConfigArguments::ConsistencyCheckSourceMode(_) |
arg @ ConfigArguments::DisableAssemblyUpdater(_) |
arg @ ConfigArguments::DisableGpuSkinning |
arg @ ConfigArguments::DisablePlaybackEngines(_) |
arg @ ConfigArguments::ExecMethod(_) |
arg @ ConfigArguments::ExportPackage(_) |
arg @ ConfigArguments::ImportPackage(_) |
arg @ ConfigArguments::JobWorkerCount(_) |
arg @ ConfigArguments::GcHelperCount(_) |
arg @ ConfigArguments::LogFile(_) |
arg @ ConfigArguments::NoUpm |
arg @ ConfigArguments::OpenFile(_) |
arg @ ConfigArguments::Password(_) |
arg @ ConfigArguments::ProjectPath(_) |
arg @ ConfigArguments::Quit |
arg @ ConfigArguments::ReleaseCodeOptimization |
arg @ ConfigArguments::SetDefaultPlatformTextureFormat |
arg @ ConfigArguments::OverrideMaxTextureSize(_) |
arg @ ConfigArguments::OverrideTextureCompression(_) |
arg @ ConfigArguments::SilentCrashes |
arg @ ConfigArguments::UpmLogFile(_) |
arg @ ConfigArguments::Username(_) |
arg @ ConfigArguments::VcsMode |
arg @ ConfigArguments::Version |
arg @ ConfigArguments::Timestamps => {
args.push(UnityArgument::Configuration(arg));
},
arg @ BatchModeArguments::AcceptApiUpdate |
arg @ BatchModeArguments::BatchMode |
arg @ BatchModeArguments::IgnoreCompileErrors |
arg @ BatchModeArguments::NoGraphics => {
args.push(UnityArgument::BatchMode(arg));
},
arg @ BuildArguments::ActiveBuildProfile(_) |
arg @ BuildArguments::Build(_) |
arg @ BuildArguments::BuildLinux64Player(_) |
arg @ BuildArguments::BuildLinuxHeadlessSimulation(_) |
arg @ BuildArguments::BuildOSXUniversalPlayer(_) |
arg @ BuildArguments::BuildTarget(_) |
arg @ BuildArguments::StandaloneBuildSubtarget(_) |
arg @ BuildArguments::BuildWindowsPlayer(_) |
arg @ BuildArguments::BuildWindows64Player(_) => {
args.push(UnityArgument::Build(arg));
},
arg @ CacheServerArguments::Enable |
arg @ CacheServerArguments::Endpoint(_, _) |
arg @ CacheServerArguments::NamespacePrefix(_) |
arg @ CacheServerArguments::EnableDownload(_) |
arg @ CacheServerArguments::EnableUpload(_) |
arg @ CacheServerArguments::WaitForConnection(_) |
arg @ CacheServerArguments::WaitForUploadCompletion |
arg @ CacheServerArguments::DownloadBatchSize(_) |
arg @ CacheServerArguments::UploadAllRevisions |
arg @ CacheServerArguments::UploadExistingShaderCache => {
args.push(UnityArgument::CacheServer(arg));
},
arg @ DebugArguments::DisableManagedDebugger |
arg @ DebugArguments::DiagDebugShaderCompiler |
arg @ DebugArguments::DebugCodeOptimization |
arg @ DebugArguments::EnableCodeCoverage |
arg @ DebugArguments::ForceD3d12Debug |
arg @ DebugArguments::ForceD3d12DebugGbv |
arg @ DebugArguments::ForceVulkanLayers |
arg @ DebugArguments::StackTraceLogType(_) |
arg @ DebugArguments::LogMemoryPerformanceStats |
arg @ DebugArguments::WaitForManagedDebugger |
arg @ DebugArguments::WaitForNativeDebugger => {
args.push(UnityArgument::Debug(arg));
},
arg @ GraphicsApiArguments::ForceClamped |
arg @ GraphicsApiArguments::ForceD3d11 |
arg @ GraphicsApiArguments::ForceD3d12 |
arg @ GraphicsApiArguments::ForceDeviceIndex |
arg @ GraphicsApiArguments::ForceGlCore |
arg @ GraphicsApiArguments::ForceGlCoreXy |
arg @ GraphicsApiArguments::ForceGlEs |
arg @ GraphicsApiArguments::ForceGlEsXy |
arg @ GraphicsApiArguments::ForceOpenGl |
arg @ GraphicsApiArguments::ForceVulkan => {
args.push(UnityArgument::GraphicsApi(arg));
},
arg @ LicenseArguments::CreateManualActivationFile |
arg @ LicenseArguments::ManualLicenseFile(_) |
arg @ LicenseArguments::ReturnLicense |
arg @ LicenseArguments::Serial(_) => {
args.push(UnityArgument::License(arg));
},
arg @ MetalArguments::ForceLowPowerDevice |
arg @ MetalArguments::ForceMetal |
arg @ MetalArguments::EnableMetalCapture => {
args.push(UnityArgument::Metal(arg));
},
arg @ ProfilerArguments::DeepProfiling |
arg @ ProfilerArguments::Enable |
arg @ ProfilerArguments::LogFile(_) |
arg @ ProfilerArguments::CaptureFrameCount(_) |
arg @ ProfilerArguments::MaxUsedMemory(_) => {
args.push(UnityArgument::Profiler(arg));
},
arg @ EditorSpecialArguments::EnableIncompatibleAssetDowngrade |
arg @ EditorSpecialArguments::GiCustomCacheLocation(_) => {
args.push(UnityArgument::Editor(arg));
},
arg @ UnityArgument::Configuration(_) |
arg @ UnityArgument::BatchMode(_) |
arg @ UnityArgument::Build(_) |
arg @ UnityArgument::CacheServer(_) |
arg @ UnityArgument::Debug(_) |
arg @ UnityArgument::GraphicsApi(_) |
arg @ UnityArgument::License(_) |
arg @ UnityArgument::Metal(_) |
arg @ UnityArgument::Profiler(_) |
arg @ UnityArgument::Editor(_) |
arg @ UnityArgument::Extra(_) => {
args.push(arg);
},
s: OsString => {
args.push(UnityArgument::Extra(s));
},
_ => compile_error!("Unsupported argument type in unity_args! macro. Ensure it's a valid argument enum variant or a String."),
}
)*
UnityArguments(args)
}
};
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EditorCommand {}
#[cfg(test)]
mod tests {
use std::{ffi::OsString, path::PathBuf};
use osstr_traits::ToOsString;
use crate::{
collection::CommandArgs,
editor_command::{
ConfigurationArgument, ExportPackage, PerforceArgument, PlatformTextureFormat,
SharedLogLevel, TextureCompression, VcsMode,
},
};
#[test]
fn config_args() {
let arg = ConfigurationArgument::CreateProject(PathBuf::from("./test-project"));
assert_eq!(arg.to_os_string(), "-createProject \"./test-project\"");
let arg = ConfigurationArgument::ConsistencyCheck;
assert_eq!(arg.to_os_string(), "-consistencyCheck");
let arg = ConfigurationArgument::ConsistencyCheckSourceMode(
super::ConsistencyCheckSourceMode::Local,
);
assert_eq!(arg.to_os_string(), "-consistencyCheckSourceMode local");
let arg = ConfigurationArgument::ConsistencyCheckSourceMode(
super::ConsistencyCheckSourceMode::CacheServer,
);
assert_eq!(
arg.to_os_string(),
"-consistencyCheckSourceMode cacheserver"
);
let arg =
ConfigurationArgument::DisableAssemblyUpdater(vec!["a.dll".into(), "b.dll".into()]);
assert_eq!(arg.to_os_string(), "-disable-assembly-updater a.dll b.dll");
let arg = ConfigurationArgument::DisableGpuSkinning;
assert_eq!(arg.to_os_string(), "-disable-gpu-skinning");
let arg = ConfigurationArgument::DisablePlaybackEngines(vec![
"AndroidPlayer".into(),
"iOSSupport".into(),
]);
assert_eq!(
arg.to_os_string(),
"-disable-playback-engines AndroidPlayer iOSSupport"
);
let arg = ConfigurationArgument::ExecMethod("NamespaceName.ClassName.MethodName".into());
assert_eq!(
arg.to_os_string(),
"-executeMethod NamespaceName.ClassName.MethodName"
);
let arg = ConfigurationArgument::ExportPackage(ExportPackage {
path: "export.unitypackage".into(),
exports: vec!["Assets/a.asset".into(), "Assets/b.asset".into()],
});
assert_eq!(
arg.to_os_string(),
"-exportPackage Assets/a.asset Assets/b.asset export.unitypackage"
);
let arg = ConfigurationArgument::ImportPackage(PathBuf::from("./package.unitypackage"));
assert_eq!(arg.to_os_string(), "-importPackage ./package.unitypackage");
let arg = ConfigurationArgument::JobWorkerCount(8usize);
assert_eq!(arg.to_os_string(), "-job-worker-count 8");
let arg = ConfigurationArgument::GcHelperCount(12usize);
assert_eq!(arg.to_os_string(), "-gc-helper-count 12");
let arg = ConfigurationArgument::LogFile(PathBuf::from("/var/log/unity/editor.log"));
assert_eq!(arg.to_os_string(), "-logFile /var/log/unity/editor.log");
let arg = ConfigurationArgument::NoUpm;
assert_eq!(arg.to_os_string(), "-noUpm");
let arg = ConfigurationArgument::OpenFile(PathBuf::from("./scene.unity"));
assert_eq!(arg.to_os_string(), "-openfile ./scene.unity");
let arg = ConfigurationArgument::Password(OsString::from("hunter12"));
assert_eq!(arg.to_os_string(), "-password hunter12");
let arg = ConfigurationArgument::ProjectPath(PathBuf::from("./my-project/"));
assert_eq!(arg.to_os_string(), "-projectPath ./my-project/");
let arg = ConfigurationArgument::Quit;
assert_eq!(arg.to_os_string(), "-quit");
let arg = ConfigurationArgument::ReleaseCodeOptimization;
assert_eq!(arg.to_os_string(), "-releaseCodeOptimization");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Dxt);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat dxt");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Atc);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat atc");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Etc);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat etc");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Etc2);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat etc2");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Pvrtc);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat pvrtc");
let arg =
ConfigurationArgument::SetDefaultPlatformTextureFormat(PlatformTextureFormat::Astc);
assert_eq!(arg.to_os_string(), "-setDefaultPlatformTextureFormat astc");
let arg = ConfigurationArgument::OverrideMaxTextureSize(256usize);
assert_eq!(arg.to_os_string(), "-overrideMaxTextureSize 256");
let arg = ConfigurationArgument::OverrideTextureCompression(TextureCompression::NoOverride);
assert_eq!(arg.to_os_string(), "-overrideTextureCompression NoOverride");
let arg = ConfigurationArgument::OverrideTextureCompression(
TextureCompression::ForceUncompressed,
);
assert_eq!(
arg.to_os_string(),
"-overrideTextureCompression ForceUncompressed"
);
let arg = ConfigurationArgument::OverrideTextureCompression(
TextureCompression::ForceFastCompressor,
);
assert_eq!(
arg.to_os_string(),
"-overrideTextureCompression ForceFastCompressor"
);
let arg = ConfigurationArgument::OverrideTextureCompression(
TextureCompression::ForceNoCrunchCompression,
);
assert_eq!(
arg.to_os_string(),
"-overrideTextureCompression ForceNoCrunchCompression"
);
let arg = ConfigurationArgument::SilentCrashes;
assert_eq!(arg.to_os_string(), "-silent-crashes");
let arg = ConfigurationArgument::UpmLogFile(PathBuf::from("/var/log/unity/upm.log"));
assert_eq!(arg.to_os_string(), "-upmLogFile /var/log/unity/upm.log");
let arg = ConfigurationArgument::Username(OsString::from("kitsucafe"));
assert_eq!(arg.to_os_string(), "-username kitsucafe");
let arg = ConfigurationArgument::VcsMode(VcsMode::VisibleMetaFiles);
assert_eq!(arg.to_os_string(), "-vcsMode \"Visible Meta Files\"");
let arg = ConfigurationArgument::VcsMode(VcsMode::HiddenMetaFiles);
assert_eq!(arg.to_os_string(), "-vcsMode \"Hidden Meta Files\"");
let arg = ConfigurationArgument::VcsMode(VcsMode::PlasticScm);
assert_eq!(arg.to_os_string(), "-vcsMode PlasticSCM");
let arg = ConfigurationArgument::VcsMode(VcsMode::Perforce(CommandArgs(vec![
PerforceArgument::Workspace("TestWorkspace".into()),
PerforceArgument::Host("Hostname".into()),
PerforceArgument::Server("8.8.8.8".into()),
PerforceArgument::SharedLogLevel(SharedLogLevel::Info),
PerforceArgument::Username("kitsucafe".into()),
PerforceArgument::Password("hunter12".into()),
])));
assert_eq!(
arg.to_os_string(),
"-vcsMode Perforce -vcPerforceWorkspace TestWorkspace -vcPerforceHost Hostname -vcPerforceServer 8.8.8.8 -vcSharedLogLevel info -vcPerforceUsername kitsucafe -vcPerforcePassword hunter12"
);
let arg = ConfigurationArgument::Version;
assert_eq!(arg.to_os_string(), "-version");
let arg = ConfigurationArgument::Timestamps;
assert_eq!(arg.to_os_string(), "-timestamps");
}
}

113
core/src/error.rs Normal file
View file

@ -0,0 +1,113 @@
use std::{error::Error as StdError, fmt::Display, path::PathBuf};
use toml::de::Error as TomlError;
#[derive(Debug)]
pub struct ShellExpansionError {
variable: String,
context: Option<String>,
}
impl ShellExpansionError {
pub fn new(variable: &str, context: Option<&str>) -> Self {
Self {
variable: variable.into(),
context: context.map(Into::into),
}
}
}
impl Display for ShellExpansionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(context) = &self.context {
write!(f, "Error expanding {} in {}", self.variable, context)
} else {
write!(f, "Error expanding {}", self.variable)
}
}
}
#[derive(Debug)]
pub struct FileError {
path: PathBuf,
source: Box<dyn StdError>,
}
impl FileError {
pub fn new(path: impl Into<PathBuf>, source: impl Into<Box<dyn StdError>>) -> Self {
Self {
path: path.into(),
source: source.into(),
}
}
}
impl Display for FileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error handling \"{}\": {}",
self.path.display(),
&self.source
)
}
}
impl std::error::Error for FileError {}
#[derive(Debug)]
pub enum ProjectManifestError {
Read(FileError),
Parse(TomlError),
}
impl From<FileError> for ProjectManifestError {
fn from(value: FileError) -> Self {
Self::Read(value)
}
}
impl From<TomlError> for ProjectManifestError {
fn from(value: TomlError) -> Self {
Self::Parse(value)
}
}
impl std::fmt::Display for ProjectManifestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read(error) => write!(f, "Error reading project manifest: {error}"),
Self::Parse(error) => write!(f, "Error parsing project manifest: {error}"),
}
}
}
impl std::error::Error for ProjectManifestError {}
#[derive(Debug)]
pub enum Error {
LoadConfig(confique::Error),
ProjectManifest(ProjectManifestError),
}
impl From<confique::Error> for Error {
fn from(value: confique::Error) -> Self {
Self::LoadConfig(value)
}
}
impl From<ProjectManifestError> for Error {
fn from(value: ProjectManifestError) -> Self {
Self::ProjectManifest(value)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::LoadConfig(error) => write!(f, "Couldn't load config: {error}"),
Error::ProjectManifest(error) => write!(f, "Couldn't read projects: {error}"),
}
}
}
impl std::error::Error for Error {}

101
core/src/lib.rs Normal file
View file

@ -0,0 +1,101 @@
#![feature(min_specialization)]
pub mod collection;
pub mod config;
pub mod editor;
pub mod editor_command;
pub mod error;
pub mod project;
use std::{fs, ops::Deref, path::Path};
use serde::{Deserialize, Serialize};
use crate::{
config::Cfg,
editor::Editor,
error::{Error, FileError, ProjectManifestError},
project::Project,
};
#[derive(Debug, Serialize, Deserialize)]
pub struct Projects {
projects: Vec<Project>,
}
impl Projects {
pub fn new(projects: Vec<Project>) -> Self {
Self { projects }
}
}
impl From<Vec<Project>> for Projects {
fn from(value: Vec<Project>) -> Self {
Self::new(value)
}
}
impl Default for Projects {
fn default() -> Self {
Self::new(Vec::new())
}
}
impl Deref for Projects {
type Target = Vec<Project>;
fn deref(&self) -> &Self::Target {
&self.projects
}
}
#[derive(Debug)]
pub struct TormentNexus {
config: Cfg,
projects: Projects,
}
impl TormentNexus {
pub fn new(config: Cfg, projects: impl Into<Projects>) -> Self {
Self {
config,
projects: projects.into(),
}
}
pub fn load() -> Result<Self, Error> {
let config = Cfg::from_env()?;
let projects = Self::read_projects(&config.project.manifest_path)?;
Ok(Self::new(config, projects))
}
pub fn read_projects(
manifest_path: impl AsRef<Path>,
) -> Result<Projects, ProjectManifestError> {
let content = fs::read_to_string(&manifest_path)
.map_err(|err| FileError::new(manifest_path.as_ref(), err))?;
Ok(toml::from_str::<Projects>(&content)?)
}
pub fn installed_editors(&self) -> Vec<Editor> {
Vec::new()
}
pub fn projects(&self) -> &Projects {
&self.projects
}
}
#[cfg(test)]
mod tests {
use crate::TormentNexus;
#[test]
fn without_cfg() {
let hub = TormentNexus::load();
println!("{hub:?}");
assert!(hub.is_ok())
}
}

10
core/src/project.rs Normal file
View file

@ -0,0 +1,10 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Project {
pub name: String,
pub editor: String,
pub path: PathBuf,
}

6
gui/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "gui"
version = "0.1.0"
edition = "2024"
[dependencies]

3
gui/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"