Commit 0e96c737 authored by shrabbit's avatar shrabbit
Browse files

feat(PlaygroundLauncher/AllColor): PlaygroundLauncher 支持 Init() 读取...

 feat(PlaygroundLauncher/AllColor): PlaygroundLauncher 支持 Init() 读取 .testvdisable 禁用列表,AllColor 新增 --output 输出模式(pixel/line/full)
parent 4efb239f
Loading
Loading
Loading
Loading
+75 −26
Original line number Diff line number Diff line
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Spectre.Console;
using TestV.Tools;

@@ -16,6 +18,41 @@ public class PlaygroundLauncher(Action onReload)
    const string None = "[red1]None[/]";
    private bool _exit;

    private readonly HashSet<string> _disable = [];
    private readonly List<string> _disableList = [];

    private void ReadDisable(string file)
    {
        try
        {
            var node = JsonNode.Parse(File.ReadAllText(file));
            if (node is not null && node.GetValueKind() == JsonValueKind.Array)
            {
                foreach (var item in node.AsArray())
                {
                    if (item is not null && item.GetValueKind() == JsonValueKind.String)
                    {
                        _disable.Add(item.ToString().ToLower());
                    }
                }
            }
        }
        catch (JsonException e)
        {

        }
    }

    public PlaygroundLauncher Init()
    {
        // ReSharper disable once StringLiteralTypo
        ReadDisable(".testvdisable");
        // ReSharper disable once StringLiteralTypo
        ReadDisable(Path.Join(AppDomain.CurrentDomain.BaseDirectory, ".testvdisable"));

        return this;
    }

    /// <summary>
    /// 初始化PlaygroundLauncher并开始运行。此方法首先根据每个游乐场实例的LaunchCmd属性对游乐场列表进行排序,然后将列表转换为只读集合。
    /// 随后显示欢迎信息。如果命令行参数中包含"--list",则还会显示所有游乐场的表格视图。最后,进入处理用户输入提示的状态。
@@ -44,6 +81,12 @@ public class PlaygroundLauncher(Action onReload)
       if(args.Contains("--list"))
           DrawTable();

       foreach (var cmd in _disableList)
       {
           // ReSharper disable once StringLiteralTypo
           AnsiConsole.MarkupLine($"[red]'{cmd}' 已由全局或工作路径 .testvdisable 禁用。[/]");
       }
       
       HandlePrompt();
    }

@@ -300,33 +343,12 @@ public class PlaygroundLauncher(Action onReload)
        var origin = new TP();
        var tp = new PlaygroundRunner<TP>(origin, origin is IPlaygroundKeepInstance);

        if (_maps.TryGetValue(tp.LaunchCmd.ToLower(), out _))
            throw new ArgumentException($"重复定义了相同命令的 Playground: '{tp.LaunchCmd.ToLower()}'");
        
        _playgrounds.Add(tp);
        _maps[tp.LaunchCmd.ToLower()] = tp;

        if (_disable.Contains(tp.LaunchCmd.ToLower()))
        {
            _disableList.Add(tp.LaunchCmd.ToLower());
            return this;
        }
        
    /// <summary>
    /// 向PlaygroundLauncher中添加一个新的游乐场实例。此方法首先检查是否已经运行了Startup()方法,如果是,则抛出异常。
    /// 然后,它使用传入的实例创建一个PlaygroundRunner包装器(如果该游乐场实现了IPlaygroundKeepInstance接口,则不会被销毁)。
    /// 接下来,确保没有其他游乐场具有相同的LaunchCmd值以避免冲突。最后,将新游乐场添加到_playgrounds列表和_maps字典中,并返回当前的PlaygroundLauncher实例以便于链式调用。
    /// </summary>
    /// <typeparam name="TP">实现IPlayground接口的具体类型。</typeparam>
    /// <param name="instance">要添加的具体游乐场实例。</param>
    /// <returns>返回当前的PlaygroundLauncher实例,支持链式调用。</returns>
    /// <exception cref="Exception">当尝试在运行了Startup()之后添加新的游乐场时抛出。</exception>
    /// <exception cref="ArgumentException">当试图添加一个与现有游乐场具有相同LaunchCmd值的新游乐场时抛出。</exception>
    public PlaygroundLauncher Add<TP>(TP instance) where TP : class, IPlayground, new()
    {
        if (_running)
            throw new Exception("无法在运行 Startup() 后添加 Playground.");

        // 默认行为:用后由 PlaygroundRunner 包装器销毁,加上 IPlaygroundKeepInstance 避免销毁。
        var tp = instance;

        if (_maps.TryGetValue(tp.LaunchCmd.ToLower(), out _))
            throw new ArgumentException($"重复定义了相同命令的 Playground: '{tp.LaunchCmd.ToLower()}'");
        
@@ -336,6 +358,33 @@ public class PlaygroundLauncher(Action onReload)
        return this;
    }

    // /// <summary>
    // /// 向PlaygroundLauncher中添加一个新的游乐场实例。此方法首先检查是否已经运行了Startup()方法,如果是,则抛出异常。
    // /// 然后,它使用传入的实例创建一个PlaygroundRunner包装器(如果该游乐场实现了IPlaygroundKeepInstance接口,则不会被销毁)。
    // /// 接下来,确保没有其他游乐场具有相同的LaunchCmd值以避免冲突。最后,将新游乐场添加到_playgrounds列表和_maps字典中,并返回当前的PlaygroundLauncher实例以便于链式调用。
    // /// </summary>
    // /// <typeparam name="TP">实现IPlayground接口的具体类型。</typeparam>
    // /// <param name="instance">要添加的具体游乐场实例。</param>
    // /// <returns>返回当前的PlaygroundLauncher实例,支持链式调用。</returns>
    // /// <exception cref="Exception">当尝试在运行了Startup()之后添加新的游乐场时抛出。</exception>
    // /// <exception cref="ArgumentException">当试图添加一个与现有游乐场具有相同LaunchCmd值的新游乐场时抛出。</exception>
    // public PlaygroundLauncher Add<TP>(TP instance) where TP : class, IPlayground, new()
    // {
    //     if (_running)
    //         throw new Exception("无法在运行 Startup() 后添加 Playground.");
    //
    //     // 默认行为:用后由 PlaygroundRunner 包装器销毁,加上 IPlaygroundKeepInstance 避免销毁。
    //     var tp = instance;
    //
    //     if (_maps.TryGetValue(tp.LaunchCmd.ToLower(), out _))
    //         throw new ArgumentException($"重复定义了相同命令的 Playground: '{tp.LaunchCmd.ToLower()}'");
    //
    //     _playgrounds.Add(tp);
    //     _maps[tp.LaunchCmd.ToLower()] = tp;
    //
    //     return this;
    // }

    /// <summary>
    /// 解析输入字符串为命令行参数数组。此方法能够处理包含空格和引号的参数。
    /// </summary>
+39 −7
Original line number Diff line number Diff line
@@ -7,14 +7,18 @@ public class AllColor : IPlayground
{
    public string LaunchCmd { get; } = "allcol";
    public string? Name { get; } = "展示所有颜色";
    public string? Description { get; } = "在 RGB/HSV/LAB 颜色空间生成渐变(默认 RGB)。";
    public string? Description { get; } = "在 RGB/HSV/LAB 颜色空间生成渐变(默认 RGB,输出可选单像素/行/全屏)。";

    private enum Mode { Rgb, Hsv, Lab }
    private enum Output { Pixel, Line, Full }

    public async Task Run(string[] args)
    {
        int cpc = 255;
        var mode = Mode.Rgb;
        var output = Output.Pixel;

        Console.Clear();

        for (int i = 0; i < args.Length; i++)
        {
@@ -28,11 +32,20 @@ public class AllColor : IPlayground
            {
                if (!TryParseMode(a["--mode=".Length..], out mode)) { Errors.NotArrow("模式", "rgb", "hsv", "lab"); return; }
            }
            else if (a is "--output" or "-o")
            {
                if (i + 1 >= args.Length) { Errors.NoArgs(1); return; }
                if (!TryParseOutput(args[++i], out output)) { Errors.NotArrow("输出", "pixel", "line", "full"); return; }
            }
            else if (a.StartsWith("--output="))
            {
                if (!TryParseOutput(a["--output=".Length..], out output)) { Errors.NotArrow("输出", "pixel", "line", "full"); return; }
            }
            else if (int.TryParse(a, out var n) && n > 0)
                cpc = n;
            else
            {
                Errors.NotArrow("参数", "--mode", "-m", "--mode=", "<正整数>");
                Errors.NotArrow("参数", "--mode", "-m", "--mode=", "--output", "-o", "--output=", "<正整数>");
                return;
            }
        }
@@ -51,10 +64,10 @@ public class AllColor : IPlayground
            }
        }, ct.Token);

        AnsiConsole.MarkupLine($"[grey]模式: {mode}  每通道采样: {cpc}  (按 Q 退出)[/]");
        AnsiConsole.MarkupLine($"[grey]模式: {mode}  输出: {output}  每通道采样: {cpc}  (按 Q 退出)[/]");
        try
        {
            await MakeGradient(mode, cpc, ct.Token);
            await MakeGradient(mode, output, cpc, ct.Token);
        }
        catch (OperationCanceledException) { }
        AnsiConsole.WriteLine();
@@ -69,9 +82,28 @@ public class AllColor : IPlayground
        return false;
    }

    private static async Task MakeGradient(Mode mode, int cpc, CancellationToken ct)
    private static bool TryParseOutput(string s, out Output output)
    {
        // 三个通道:外/中/内层循环,每层 cpc 个采样
        output = default;
        if (s.Equals("pixel", StringComparison.OrdinalIgnoreCase)) { output = Output.Pixel; return true; }
        if (s.Equals("line", StringComparison.OrdinalIgnoreCase)) { output = Output.Line; return true; }
        if (s.Equals("full", StringComparison.OrdinalIgnoreCase)) { output = Output.Full; return true; }
        return false;
    }

    private static async Task MakeGradient(Mode mode, Output output, int cpc, CancellationToken ct)
    {
        // 缓存占位字符串,避免循环内重复分配
        int w = AnsiConsole.Profile.Width;
        int h = AnsiConsole.Profile.Height;
        string placeholder = output switch
        {
            Output.Pixel => " ",
            Output.Line => new string(' ', w),
            Output.Full => new string(' ', w * h),
            _ => " "
        };

        for (int i = 0; i < cpc; i++)
        for (int j = 0; j < cpc; j++)
        for (int k = 0; k < cpc; k++)
@@ -84,7 +116,7 @@ public class AllColor : IPlayground
                Mode.Lab => LabToRgb(LabSample(i, j, k, cpc)),
                _ => throw new ArgumentOutOfRangeException()
            };
            AnsiConsole.Write(new Text(new string(' ', AnsiConsole.Profile.Width), new Style(null, new Color(r, g, b))));
            AnsiConsole.Write(new Text(placeholder, new Style(null, new Color(r, g, b))));
        }
        await Task.CompletedTask;
    }
+1 −0
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ class Program
            reload = false;
            
            var play = new PlaygroundLauncher(() => reload = true)
                .Init()
                .Add<SamplePlayground>()
                .Add<AllColor>()
                .Add<Alloc>()
+1 −0
Original line number Diff line number Diff line
["oom"]
 No newline at end of file