public void DeleteFiles() { if (_GUIDs.Count > 0) { // We need to get the editor path first string samplePath = Path.GetDirectoryName( AssetDatabase.GUIDToAssetPath(_GUIDs[0]) ); string editorPath = UnityPathUtils.GetEditorFolder(samplePath); //string absEditorPath = UnityPathUtils.RelativeToAbsolute(relEditorPath); foreach (string GUID in _GUIDs) { AssetDatabase.DeleteAsset( AssetDatabase.GUIDToAssetPath(GUID) ); } if (Directory.Exists(editorPath) && Directory.GetFiles(editorPath).Length == 0 && Directory.GetDirectories(editorPath).Length == 0 ) { AssetDatabase.DeleteAsset(editorPath); } } // End if } // End DeleteFiles
private string PromptForNewString(string initPathRel) { bool valid = false; string newPathAbs = ""; string newPathRel = ""; do { newPathAbs = EditorUtility.OpenFolderPanel("Choose a target folder", initPathRel, ""); if (string.IsNullOrEmpty(newPathAbs)) { // They must have canceled or something newPathRel = initPathRel; valid = true; } else { newPathRel = UnityPathUtils.AbsoluteToRelative(newPathAbs); valid = UnityPathUtils.IsRelativePath(newPathRel); if (!valid) { EditorUtility.DisplayDialog( title: "Invalid Destination", message: "You must pick a folder within the project's Assets folder!", ok: "OK" ); } } } while(!valid); return(newPathRel); }
/// <summary> /// Rebuild the files associated with this type based upon its meta data. /// This can potentially delete scripts if the templates don't /// explicitly build them. /// /// Scripts are dumped into the folder pointed to by DominantPath. /// </summary> /// <param name="newMetaData"> /// The new metadata to use. This simply means that the new scripts /// will use this metadata, and then we'll delete old scripts which /// haven't been overriden. It does NOT mean that files will get /// renamed. /// </param> /// <returns>The paths of the new/modified files.</returns> public List <string> RebuildFiles(ScriptMetaData newMetaData) { // We'll save the file paths for later... we can never really // trust that things won't get updated after we modify the files. string[] origPaths = GUIDs; for (int i = 0; i < origPaths.Length; i++) { origPaths[i] = UnityPathUtils.LocalizeDirectorySeparators( AssetDatabase.GUIDToAssetPath(origPaths[i]) ); } List <string> resultFiles = VariableTypeBuilder.CreateNewVariableType( newMetaData, UnityPathUtils.AbsoluteToRelative(DominantPath), overrideExisting: true ); // Once we perform the reset, we'll want to clean up any extra files // which were not in our set of templates. If we find any, // we'll make sure to delete 'em and get everything cleaned up. foreach (string origPath in origPaths) { if (!resultFiles.Contains(origPath)) { AssetDatabase.DeleteAsset(origPath); } } return(resultFiles); }
/// <summary> /// Given a path, checks if it would be part of the Editor assembly. /// (That is, if it has a folder named "Editor" in its path.) If not, /// the Editor folder is appended to the path. /// /// No checks are done to ensure that the folders actually exist, nor /// are any folders created. /// </summary> /// <returns> /// The path to the nearest editor folder. /// </returns> /// <param name="initPath">Path to closes Editor folder.</param> public static string GetEditorFolder(string initPath) { string editorPath = ""; if(!IsInEditorAssembly(initPath)) { editorPath = UnityPathUtils.Combine(initPath, "Editor"); } else { editorPath = initPath; } return editorPath; }
/// <summary> /// Tests if the path given is valid and can be stored. /// </summary> /// <returns>The new path.</returns> /// <param name="newPath">New path.</param> private static string TestNewPath(string newPath) { // This function exists because we need to be able to use this // logic inside the constructor. However, as a struct, the constructor // cannot access the path field. newPath = UnityPathUtils.AbsoluteToRelative(newPath); if (AssetDatabase.IsValidFolder(newPath)) { return(newPath); } else { return(""); } }
/// <summary> /// Converts the given relative path into an absolute path. Note that /// this only works for paths that are pointing at the "Assets" folder /// of the current project. Otherwise, the original path is returned. /// </summary> /// <returns>The to absolute.</returns> /// <param name="relPath">Rel path.</param> public static string RelativeToAbsolute(string relPath) { string absPath; if(IsRelativePath(relPath)) { absPath = relPath.Remove(0, "Assets".Length + 1); absPath = absPath.TrimStart(new char[] { '/', '\\' }); absPath = UnityPathUtils.Combine(Application.dataPath, absPath); } else { absPath = relPath; } return absPath; }
/// <summary> /// Converts the absolute path to a relative path. Note that this path /// must be pointing at the project's "Assets" folder. Otherwise, /// the original path is returned. /// </summary> /// <returns>The relative path.</returns> /// <param name="absPath">Absolute path.</param> public static string AbsoluteToRelative(string absPath) { string relPath; if(IsAbsoluteProjectPath(absPath)) { relPath = absPath.Remove(0, Application.dataPath.Length); relPath = relPath.TrimStart(new char[] { '/', '\\' }); relPath = UnityPathUtils.Combine("Assets", relPath); } else { relPath = absPath; } return relPath; }
} // End CreateNewVariableType #region File Creation /// <summary> /// Creates a script from the given template. /// /// This function does very little checking...it assumes that all names /// are valid C# for identifiers. /// /// Be wary that this may throw exceptions. (See StreamReader.ReadToEnd(), /// StreamWriter.Write(), StreamReader's contructor, and StreamWriter's /// constructor.) /// /// This does not issue a refresh, nor does it add any labels. /// </summary> /// <returns> /// The relative path (starting with the project root) to the newly /// created file. If it wasn't created (i.e. referability doesn't match /// with the types supported by the template), and empty string is /// returned instead. /// </returns> /// <param name="readableName">Human readable name.</param> /// <param name="typeName">C# name of type to be supported.</param> /// <param name="referability"> /// Referability mode associated with the C# type named by typeName. /// </param> /// <param name="template">Info for the template.</param> /// <param name="normalPath">Path for non-editor scripts.</param> /// <param name="editorPath">Path for editor scripts.</param> private static string CreateScriptFromTemplate( string readableName, string typeName, ReferabilityMode referability, TemplateInfo template, string normalPath, string editorPath ) { string templatePath = ""; // Path of the template file string newFileName = ""; // Name of the new file string newFilePath = ""; // Full path of new file (including name) // Before attempting to copy the template, we'll check if it // even matches what we need. if (template.IsCompatibleWith(referability)) { templatePath = template.path; newFileName = ReplaceTemplatePlaceholders( Path.GetFileNameWithoutExtension(templatePath), readableName, typeName, referability.ToString() ) + ".cs"; newFilePath = UnityPathUtils.Combine( (template.IsEngineTemplate ? normalPath : editorPath), newFileName ); if (File.Exists(newFilePath)) { throw new IOException(newFilePath + " already exists!"); } StreamReader templateReader = null; string templateContents = ""; try { // After changing the conde in this block, revise the // exception documentaion above. templateReader = new StreamReader(templatePath); templateContents = templateReader.ReadToEnd(); } finally { if (templateReader != null) { templateReader.Close(); } } string newScriptContents = ReplaceTemplatePlaceholders( templateContents, readableName, typeName, referability.ToString() ); StreamWriter scriptWriter = null; try { // After changing the conde in this block, revise the // exception documentaion above. scriptWriter = new StreamWriter(newFilePath); scriptWriter.Write(newScriptContents); } finally { if (scriptWriter != null) { scriptWriter.Close(); } } } // End if( ... ) return(newFilePath); } // End CreateScriptFromTemplate
/// <summary> /// Creates the new type of the variable based on the given parameters. /// After getting all of the correct templates, they will be copied /// into the folder specified by targetPath. /// /// Some templates must be placed in an Editor folder. If one does not /// exist, one might be created. However, this function will avoid /// overwriting files. Once it is done, it will refresh the project. /// /// Note that not all of the listen exceptions may get thrown. There /// are others which may get thrown by the file IO methods. /// </summary> /// /// <exception cref="System.ArgumentOutOfRangeException"> /// Thrown if any of the arguments are invalid. (See each argument /// for details.) /// </exception> /// /// <exception cref="System.IO.DirectoryNotFoundException"> /// If the targetPath is invalid. /// </exception> /// /// <exception cref="System.IO.IOException"> /// Thrown if the Editor folder cannot be created. Also thrown if any /// of the files we run into a pre-existing file. /// </exception> /// /// <param name="readableName"> /// The human readable name. Must be a legal C# name, and must be legal /// when used as a filename. The first character should be uppercase. /// If any of the checks fail or if the name is already taken /// by another variable object, then an ArgumentOutOfRangeException is /// thrown. /// </param> /// <param name="typeName"> /// The name of the pre-existing C# type to be represtented. If the /// string fails the checks, then an ArgumentOutOfRangeException is thrown. /// </param> /// <param name="referability"> /// Whether the C# type refered to by typeName is referable or not. /// If ReferabilityMode.Unknown is passed, ArgumentOutOfRangeException is thrown. /// </param> /// <param name="targetPath"> /// Path used when attempting to create the new files. If this isn't a /// valid path, a DirectoryNotFoundException is thrown. If the /// target folder is nested in an Editor folder (or is an Editor folder /// itself), a ArgumentOutOfRangeException is thrown instead. /// </param> public static void CreateNewVariableType( string readableName, string typeName, ReferabilityMode referability, string targetPath ) { // We'll use this to track any new files we make at any point, // which makes it easy to clean up after ourselves if things // go awry. List <string> newFilePaths = new List <string>(); string editorPath = UnityPathUtils.GetEditorFolder(targetPath); if (!IsValidName(readableName)) { throw new System.ArgumentOutOfRangeException( "readableName", "Either contains invalid characters or could conflict with" + " a C# keyword." ); } else if (ScriptFileManager.IsNameTaken(readableName)) { throw new System.ArgumentOutOfRangeException( "readableName", "Is already taken by another VariableObject type." ); } else if (!IsValidName(typeName)) { throw new System.ArgumentOutOfRangeException( "typeName", "Either contains invalid characters or could conflict with" + " a C# keyword." ); } else if (referability == ReferabilityMode.Unknown) { throw new System.ArgumentOutOfRangeException( "referability", "Must be something other than ReferabilityMode.unknown." ); } else if (!AssetDatabase.IsValidFolder(targetPath)) { throw new DirectoryNotFoundException( "targetPath must be pointing to a pre-existing folder. If" + " you want to create a new folder to put the scripts in," + " you must do it before calling CreateNewVariableType." ); } else if (UnityPathUtils.IsInEditorAssembly(targetPath)) { // If we tried putting all of our scripts in an Editor folder, // it would be mostly pointless because then we cannot use our // variable objects in the final build. throw new System.ArgumentOutOfRangeException( "targetPath", "Must not be nested in an Editor folder." ); } else if (File.Exists(editorPath) && !AssetDatabase.IsValidFolder(editorPath)) { throw new IOException( editorPath + " exists as a file, so we cannot make a folder there!" ); } // It's still possible that the editor folder doesn't exist. // However, we are sure that there isn't a file with that name, // so we can go ahead and create it if necessary. if (!AssetDatabase.IsValidFolder(editorPath)) { editorPath = AssetDatabase.GUIDToAssetPath( AssetDatabase.CreateFolder(targetPath, "Editor") ); newFilePaths.Add(editorPath); } // At this point, everything is valid. Barring some kind of error // when writting to the disk, everything should be good to go. // // The only thing we are not going to check is if the files already // exist. We could check this before-hand, but this makes it a bit // more complex overall. We don't even gain very much in doing this; // we still need to handle an exception thrown by the file-writing // code, which may require cleaning up files. // // We shall build a list of all of the files which we create. That // way, we can delete them if something goes wrong. // // // Until after the AssetDatabase.Refresh in the finally block, // we cannot rely on Unity's AssetDatabase class. This only works // on files which are part of the project. However, if start we // refreshing, it'll refresh asynchroniously. Deleting files // during a refresh is risky, and may lead to errors and even lock // up Unity's reloading mechanisms. // // Fortunately, C# has enough tools to get the job done. try { EditorApplication.LockReloadAssemblies(); foreach (TemplateInfo template in TemplateFileManager.Templates) { string newScriptPath = CreateScriptFromTemplate( readableName, typeName, referability, template, targetPath, editorPath ); // Add it to the start so that, when iterating through // later on, the new folder would be at the back. if (!string.IsNullOrEmpty(newScriptPath)) { newFilePaths.Insert(0, newScriptPath); } } // End foreach(TemplateInfo template in Files.Templates) } // End try catch (System.Exception e) { foreach (string path in newFilePaths) { if ((File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory) { Directory.Delete(path); } else { File.Delete(path); } if (File.Exists(path + ".meta")) { File.Delete(path + ".meta"); } } throw e; } finally { EditorApplication.UnlockReloadAssemblies(); AssetDatabase.Refresh(); } SetFileLabels(newFilePaths.ToArray()); } // End CreateNewVariableType
} // End CreateNewVariableType #region File Creation /// <summary> /// Creates a script from the given template. /// /// This function does very little checking...it assumes that all names /// are valid C# for identifiers. /// /// Be wary that this may throw exceptions. (See StreamReader.ReadToEnd(), /// StreamWriter.Write(), StreamReader's contructor, and StreamWriter's /// constructor.) /// /// This does not issue a refresh, nor does it add any labels. /// </summary> /// <returns> /// The relative path (starting with the project root) to the newly /// created file. If it wasn't created (i.e. referability doesn't match /// with the types supported by the template), and empty string is /// returned instead. /// </returns> /// <param name="metaData">Data used to populate the template.</param> /// <param name="template">Info for the template.</param> /// <param name="normalPath">Path for non-editor scripts.</param> /// <param name="editorPath">Path for editor scripts.</param> private static string CreateScriptFromTemplate( ScriptMetaData metaData, TemplateInfo template, string normalPath, string editorPath, bool overrideExisting = false ) { //string templatePath = ""; // Path of the template file //string newFileName = ""; // Name of the new file string newFilePath = ""; // Full path of new file (including name) // Before attempting to copy the template, we'll check if it // even matches what we need. if (template.IsCompatibleWith(metaData.ParsedReferability)) { string templateName = Path.GetFileNameWithoutExtension(template.path); string newFileName = metaData.ApplyReplacements(templateName) + ".cs"; newFilePath = UnityPathUtils.Combine( (template.IsEngineTemplate ? normalPath : editorPath), newFileName ); if (File.Exists(newFilePath) && !overrideExisting) { throw new IOException(newFilePath + " already exists!"); } StreamReader templateReader = null; string templateContents = ""; try { // After changing the conde in this block, revise the // exception documentaion above. templateReader = new StreamReader(template.path); templateContents = templateReader.ReadToEnd(); } finally { if (templateReader != null) { templateReader.Close(); } } string newScriptContents = metaData.ApplyReplacements(templateContents); StreamWriter scriptWriter = null; try { // After changing the conde in this block, revise the // exception documentaion above. scriptWriter = new StreamWriter(newFilePath, append: false); scriptWriter.Write(newScriptContents + TemplateNewLine); // Append the meta data. scriptWriter.Write(ScriptSetManager.DataHeader + TemplateNewLine); scriptWriter.Write(metaData.ToJson() + TemplateNewLine); scriptWriter.Write(ScriptSetManager.DataFooter + TemplateNewLine); } finally { if (scriptWriter != null) { scriptWriter.Close(); } } } // End if( ... ) return(newFilePath); } // End CreateScriptFromTemplate
/// <summary> /// Creates the new type of the variable based on the given parameters. /// After getting all of the correct templates, they will be copied /// into the folder specified by targetPath. /// /// Some templates must be placed in an Editor folder. If one does not /// exist, one might be created. However, this function will avoid /// overwriting files. Once it is done, it will refresh the project. /// /// Note that not all of the listen exceptions may get thrown. There /// are others which may get thrown by the file IO methods. /// </summary> /// /// <exception cref="System.ArgumentOutOfRangeException"> /// Thrown if any of the arguments are invalid. (See each argument /// for details.) /// </exception> /// /// <exception cref="System.IO.DirectoryNotFoundException"> /// If the targetPath is invalid. /// </exception> /// /// <exception cref="System.IO.IOException"> /// Thrown if the Editor folder cannot be created. Also thrown if any /// of the files we run into a pre-existing file. /// </exception> /// /// <param name="metaData"> /// All of the data to use for the new scripts. /// /// metaData.name: Must be a legal C# name, and must be legal /// when used as a filename. The first character should be uppercase. /// If any of the checks fail or if the name is already taken /// by another variable object, then an ArgumentOutOfRangeException is /// thrown. /// /// metaData.type: The name of the pre-existing C# type to be represtented. If /// the string fails the checks, then an ArgumentOutOfRangeException is thrown. /// /// metaData.ParsedReferability: Whether the C# type refered to by typeName is /// a class or a struct. If ReferabilityMode.Unknown is passed, /// ArgumentOutOfRangeException is thrown. /// </param> /// /// <param name="targetPath"> /// Path used when attempting to create the new files. If this isn't a /// valid path, a DirectoryNotFoundException is thrown. If the /// target folder is nested in an Editor folder (or is an Editor folder /// itself), a ArgumentOutOfRangeException is thrown instead. /// </param> /// /// <param name="overrideExisting"> /// If true, this will NOT check for scripts which already exist, and will /// happilly override whatever it finds. This will NOT change GUIDs. /// </param> /// /// <returns>A list of all the files created.</returns> public static List <string> CreateNewVariableType( ScriptMetaData metaData, string targetPath, bool overrideExisting = false ) { // This is the path of the editor folder we'll use to dump our // editor scripts into. string editorPath = UnityPathUtils.GetEditorFolder(targetPath); #region Error checking if (!IsValidName(metaData.name)) { throw new System.ArgumentOutOfRangeException( "readableName", "Either contains invalid characters or could conflict with" + " a C# keyword." ); } else if (ScriptSetManager.IsNameTaken(metaData.name) && !overrideExisting) { throw new System.ArgumentOutOfRangeException( "readableName", "Is already taken by another VariableObject type." ); } else if (!IsValidName(metaData.type)) { throw new System.ArgumentOutOfRangeException( "typeName", "Either contains invalid characters or could conflict with" + " a C# keyword." ); } else if (metaData.ParsedReferability == ReferabilityMode.Unknown) { throw new System.ArgumentOutOfRangeException( "referability", "Must be something other than ReferabilityMode.unknown." ); } else if (!AssetDatabase.IsValidFolder(targetPath) && !Directory.Exists(targetPath)) { // TODO This seems mildly buggy on Windows. I created a folder // inside the folder-select prompt and it complained about // it. Maybe, instead of using AssetDatabase, we should use // normal C# checks? throw new DirectoryNotFoundException( "targetPath must be pointing to a pre-existing folder. If" + " you want to create a new folder to put the scripts in," + " you must do it before calling CreateNewVariableType." + "(Trying to use: " + targetPath + ")" ); } else if (UnityPathUtils.IsInEditorAssembly(targetPath)) { // If we tried putting all of our scripts in an Editor folder, // it would be mostly pointless because then we cannot use our // variable objects in the final build. throw new System.ArgumentOutOfRangeException( "targetPath", "Must not be nested in an Editor folder." ); } else if (File.Exists(editorPath) && !AssetDatabase.IsValidFolder(editorPath)) { throw new IOException( editorPath + " exists as a file, so we cannot make a folder there!" ); } #endregion /* * At this point, everything is valid. Barring some kind of error * when writting to the disk, everything should be good to go. * * The only thing we are not going to check is if the files already * exist. We could check this before-hand, but this makes it a bit * more complex overall. We don't even gain very much in doing this; * we still need to handle an exception thrown by the file-writing * code, which may require cleaning up files. * * We shall build a list (see newFIlePaths, below) of all of the * files which we create. That way, we can delete them if something * goes wrong. * * * Until calling AssetDatabase.Refresh in the finally block, * we cannot rely on Unity's AssetDatabase class without risking * the stability of Unity. * * If Unity begins a refresh (on its own accord), this process * occurs asynchroniously. In other words, it may start refreshing * right in the middle of this function execution. * * This is normally okay, but if we catch an exception, we need * to delete all of the files we created. Deleting files during a * refresh is risky, and may lead to errors and even lock up * Unity's reloading mechanisms, halting the Unity Editor. * * Fortunately, C# has enough tools to get the job done. Still, * it's a bit less robust than using the AssetDatabase. Also, we * cannot set labels until the very end of the process. * * (Again, we can't just check ahead of time. There's always risk * of an exception getting thrown while doing file IO.) */ // Used to track the new files we add so that we can clean them up // if necessary. This is a stack so that the first most recent // things we create get deleted first. Stack <string> newFilePaths = new Stack <string>(); try { // It's still possible that the editor folder doesn't exist. // However, we are sure that there isn't a file with that name, // so we can go ahead and create it if necessary. // // Note that we're doing this BEFORE locking the assemblies so // we can still use the AssetDatabase class. if (!AssetDatabase.IsValidFolder(editorPath)) { editorPath = AssetDatabase.GUIDToAssetPath( AssetDatabase.CreateFolder(targetPath, "Editor") ); // We've created the folder, so it's one of the things // we'll want to clean up if things go wrong. newFilePaths.Push(editorPath); } EditorApplication.LockReloadAssemblies(); foreach (TemplateInfo template in TemplateFileManager.Templates) { string newScriptPath = CreateScriptFromTemplate( metaData, template, targetPath, editorPath, overrideExisting ); // CreateScriptFromTemplate CAN return an empty string if // it chooses not to create a script, so we have to watch for this. if (!string.IsNullOrEmpty(newScriptPath)) { newFilePaths.Push(newScriptPath); } } // End foreach(TemplateInfo template in Files.Templates) } // End try catch (System.Exception e) { foreach (string path in newFilePaths) { if ((File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory) { Directory.Delete(path); // Possible Unity created a meta file already. if (File.Exists(path + ".meta")) { File.Delete(path + ".meta"); } } else { File.Delete(path); } } throw e; } finally { EditorApplication.UnlockReloadAssemblies(); AssetDatabase.Refresh(); } string label = metaData.builtin ? ScriptSetManager.UnityLabel : ScriptSetManager.CustomLabel; SetFileLabels(newFilePaths.ToArray(), label); return(new List <string>(newFilePaths)); } // End CreateNewVariableType