Files
AIC-Project/Client/PackageLocal/com.unity.pipeline@0.4.0-exp.1/Editor/Commands/Assets/TextFileCommands.cs
T

127 lines
5.6 KiB
C#

using System;
using System.IO;
using Newtonsoft.Json;
using Unity.Pipeline.Commands;
using Unity.Pipeline.Editor.Authoring;
using Unity.Pipeline.Models;
using UnityEditor;
namespace Unity.Pipeline.Editor.Commands.Assets
{
/// <summary>
/// Text-file authoring commands (CLI-191): read and write UTF-8 text files inside the authoring
/// sandbox. Useful for editing data assets that are plain text (JSON/CSV/.txt/.cs/.shader, etc.).
///
/// Every path is funnelled through <see cref="ProjectPaths.Resolve"/>, so reads/writes are confined
/// to the authoring root and "../"/out-of-project paths are rejected. Writing imports the file back
/// into the AssetDatabase so Unity picks up the change.
///
/// NOTE: writing is potentially destructive (it can overwrite an existing file), so overwriting an
/// existing file requires an explicit <c>confirm</c> argument; <c>dry_run</c> is supported.
/// Filesystem writes are not part of Unity's Undo system.
/// </summary>
public static class TextFileCommands
{
[CliCommand("read_text_file", "Read a UTF-8 text file under the authoring root and return its contents.", MainThreadRequired = true)]
public static ReadTextFileResult ReadTextFile(
[CliArg("path", "Text file path relative to the authoring root. The Assets/ prefix is optional.", Required = true)] string path,
[CliArg("max_bytes", "Reject files larger than this many bytes (default 1048576 = 1 MiB) to avoid huge payloads.")] int maxBytes = 1024 * 1024)
{
var normalized = ProjectPaths.Resolve(path, out var error);
if (normalized == null)
throw new ArgumentException(error);
var absolute = ToAbsolute(normalized);
if (!File.Exists(absolute))
throw new ArgumentException($"No file at '{normalized}'.");
var length = new FileInfo(absolute).Length;
if (length > maxBytes)
throw new ArgumentException($"File '{normalized}' is {length} bytes, which exceeds max_bytes={maxBytes}.");
return new ReadTextFileResult
{
AssetPath = normalized,
Bytes = (int)length,
Contents = File.ReadAllText(absolute)
};
}
[CliCommand("write_text_file", "Write UTF-8 text to a file under the authoring root, then import it. Overwriting an existing file requires confirm=true.")]
public static AuthoringResult WriteTextFile(
[CliArg("path", "Text file path relative to the authoring root, including extension. The Assets/ prefix is optional.", Required = true)] string path,
[CliArg("contents", "The full text content to write (replaces the file).", Required = true)] string contents,
[CliArg("confirm", "Required (true) only when overwriting an existing file at the path.")] bool confirm = false,
[CliArg("dry_run", "If true, validate inputs and report what would be written without writing anything.")] bool dryRun = false)
{
var normalized = ProjectPaths.Resolve(path, out var error);
if (normalized == null)
throw new ArgumentException(error);
if (contents == null)
throw new ArgumentException("contents is required (use an empty string to write an empty file).");
var absolute = ToAbsolute(normalized);
var exists = File.Exists(absolute);
if (exists && !confirm)
throw new ArgumentException($"A file already exists at '{normalized}'. Pass confirm=true to overwrite it.");
if (dryRun)
return new AuthoringResult { AssetPath = normalized, Type = "TextAsset" };
EnsureParentFolder(normalized);
File.WriteAllText(absolute, contents);
AssetDatabase.ImportAsset(normalized, ImportAssetOptions.ForceUpdate);
var loaded = AssetDatabase.LoadMainAssetAtPath(normalized);
var result = ObjectResolver.Describe(loaded) ?? new AuthoringResult { Type = "TextAsset" };
result.AssetPath = normalized;
return result;
}
private static void EnsureParentFolder(string assetPath)
{
var parent = Path.GetDirectoryName(assetPath)?.Replace('\\', '/');
if (string.IsNullOrEmpty(parent) || AssetDatabase.IsValidFolder(parent))
return;
CreateFolderRecursive(parent);
}
private static void CreateFolderRecursive(string assetsPath)
{
if (AssetDatabase.IsValidFolder(assetsPath))
return;
var parent = Path.GetDirectoryName(assetsPath)?.Replace('\\', '/');
var name = Path.GetFileName(assetsPath);
if (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(name))
throw new ArgumentException($"Invalid folder path '{assetsPath}'.");
if (!AssetDatabase.IsValidFolder(parent))
CreateFolderRecursive(parent);
AssetDatabase.CreateFolder(parent, name);
}
private static string ToAbsolute(string projectRelative)
{
return $"{ProjectPaths.ProjectRoot.Replace('\\', '/')}/{projectRelative}";
}
}
/// <summary>Result of <c>read_text_file</c>: the file contents plus its identity and size.</summary>
[Serializable]
public class ReadTextFileResult
{
[JsonProperty("assetPath")]
public string AssetPath { get; set; }
[JsonProperty("bytes")]
public int Bytes { get; set; }
[JsonProperty("contents")]
public string Contents { get; set; }
}
}