mirror of
https://github.com/nsnail/dot.git
synced 2025-06-19 13:58:16 +08:00
<refactor> ui美化...
This commit is contained in:
@ -5,7 +5,7 @@ public sealed class Main : ToolBase<Option>
|
||||
{
|
||||
public Main(Option opt) : base(opt) { }
|
||||
|
||||
public override Task Run()
|
||||
protected override Task Core()
|
||||
{
|
||||
Application.Run(new WinMain());
|
||||
return Task.CompletedTask;
|
||||
|
@ -45,6 +45,39 @@ public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : Dir
|
||||
return fileList;
|
||||
}
|
||||
|
||||
protected override async Task Core()
|
||||
{
|
||||
if (!Opt.WriteMode) AnsiConsole.MarkupLine("[gray]{0}[/]", Str.ExerciseMode);
|
||||
IEnumerable<string> fileList;
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(new ProgressBarColumn() //
|
||||
, new ElapsedTimeColumn() //
|
||||
, new PercentageColumn() //
|
||||
, new SpinnerColumn() //
|
||||
, new TaskDescriptionColumn { Alignment = Justify.Left } //
|
||||
)
|
||||
.StartAsync(async ctx => {
|
||||
var taskSearchfile = ctx.AddTask(Str.SearchingFile).IsIndeterminate();
|
||||
_childTask = ctx.AddTask("-/-", false);
|
||||
fileList = EnumerateFiles(Opt.Path, Opt.Filter, out _excludeCnt);
|
||||
_totalCnt = fileList.Count();
|
||||
taskSearchfile.IsIndeterminate(false);
|
||||
taskSearchfile.Increment(100);
|
||||
|
||||
_childTask.MaxValue = _totalCnt;
|
||||
_childTask.StartTask();
|
||||
await Parallel.ForEachAsync(fileList, FileHandle);
|
||||
});
|
||||
|
||||
var grid = new Grid().AddColumn(new GridColumn().NoWrap().PadRight(16))
|
||||
.AddColumn(new GridColumn().Alignment(Justify.Right));
|
||||
|
||||
foreach (var kv in _writeStats.OrderByDescending(x => x.Value).ThenBy(x => x.Key))
|
||||
grid.AddRow(kv.Key, kv.Value.ToString());
|
||||
|
||||
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
|
||||
}
|
||||
|
||||
|
||||
protected FileStream CreateTempFile(out string file)
|
||||
{
|
||||
@ -94,37 +127,4 @@ public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : Dir
|
||||
{
|
||||
_writeStats.AddOrUpdate(key, 1, (_, oldValue) => oldValue + 1);
|
||||
}
|
||||
|
||||
public override async Task Run()
|
||||
{
|
||||
if (!Opt.WriteMode) AnsiConsole.MarkupLine("[gray]{0}[/]", Str.ExerciseMode);
|
||||
IEnumerable<string> fileList;
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(new ProgressBarColumn() //
|
||||
, new ElapsedTimeColumn() //
|
||||
, new PercentageColumn() //
|
||||
, new SpinnerColumn() //
|
||||
, new TaskDescriptionColumn { Alignment = Justify.Left } //
|
||||
)
|
||||
.StartAsync(async ctx => {
|
||||
var taskSearchfile = ctx.AddTask(Str.SearchingFile).IsIndeterminate();
|
||||
_childTask = ctx.AddTask("-/-", false);
|
||||
fileList = EnumerateFiles(Opt.Path, Opt.Filter, out _excludeCnt);
|
||||
_totalCnt = fileList.Count();
|
||||
taskSearchfile.IsIndeterminate(false);
|
||||
taskSearchfile.Increment(100);
|
||||
|
||||
_childTask.MaxValue = _totalCnt;
|
||||
_childTask.StartTask();
|
||||
await Parallel.ForEachAsync(fileList, FileHandle);
|
||||
});
|
||||
|
||||
var grid = new Grid().AddColumn(new GridColumn().NoWrap().PadRight(16))
|
||||
.AddColumn(new GridColumn().Alignment(Justify.Right));
|
||||
|
||||
foreach (var kv in _writeStats.OrderByDescending(x => x.Value).ThenBy(x => x.Key))
|
||||
grid.AddRow(kv.Key, kv.Value.ToString());
|
||||
|
||||
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
|
||||
}
|
||||
}
|
132
src/Git/Main.cs
132
src/Git/Main.cs
@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using NSExt.Extensions;
|
||||
@ -6,13 +7,9 @@ namespace Dot.Git;
|
||||
|
||||
public class Main : ToolBase<Option>
|
||||
{
|
||||
private const int _POS_Y_MSG = 74; //git command rsp 显示的位置 y
|
||||
private const int _POST_Y_LOADING = 70; //loading 动画显示的位置 y
|
||||
private const int _REP_PATH_LENGTH_LIMIT = 32; //仓库路径长度显示截断阈值
|
||||
private (int x, int y) _cursorPosBackup; //光标位置备份
|
||||
private readonly Encoding _gitOutputEnc; //git command rsp 编码
|
||||
private List<string> _repoPathList; //仓库目录列表
|
||||
|
||||
private readonly Encoding _gitOutputEnc; //git command rsp 编码
|
||||
private ConcurrentDictionary<string, StringBuilder> _repoRsp; //仓库信息容器
|
||||
private ConcurrentDictionary<string, TaskStatusColumn.Statues> _repoStatus;
|
||||
|
||||
public Main(Option opt) : base(opt)
|
||||
{
|
||||
@ -23,25 +20,24 @@ public class Main : ToolBase<Option>
|
||||
}
|
||||
|
||||
|
||||
private async ValueTask DirHandle(string dir, CancellationToken cancelToken)
|
||||
private async ValueTask DirHandle(KeyValuePair<string, ProgressTask> payload, CancellationToken _)
|
||||
{
|
||||
var row = _repoPathList.FindIndex(x => x == dir); // 行号
|
||||
var tAnimate = LoadingAnimate(_POST_Y_LOADING, _cursorPosBackup.y + row, out var cts);
|
||||
payload.Value.StartTask();
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Executing);
|
||||
|
||||
// 打印 git command rsp
|
||||
void ExecRspReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data is null) return;
|
||||
var msg = Encoding.UTF8.GetString(_gitOutputEnc.GetBytes(e.Data));
|
||||
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, new string(' ', Console.WindowWidth - _POS_Y_MSG));
|
||||
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, msg);
|
||||
_repoRsp[payload.Key].Append(msg.EscapeMarkup());
|
||||
}
|
||||
|
||||
// 启动git进程
|
||||
{
|
||||
var startInfo = new ProcessStartInfo {
|
||||
CreateNoWindow = true
|
||||
, WorkingDirectory = dir
|
||||
, WorkingDirectory = payload.Key
|
||||
, FileName = "git"
|
||||
, Arguments = Opt.Args
|
||||
, UseShellExecute = false
|
||||
@ -54,58 +50,78 @@ public class Main : ToolBase<Option>
|
||||
p.BeginOutputReadLine();
|
||||
p.BeginErrorReadLine();
|
||||
await p.WaitForExitAsync();
|
||||
|
||||
payload.Value.IsIndeterminate(false);
|
||||
if (p.ExitCode == 0) {
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Succeed);
|
||||
_repoStatus.AddOrUpdate(payload.Key, _ => TaskStatusColumn.Statues.Succeed
|
||||
, (_, _) => TaskStatusColumn.Statues.Succeed);
|
||||
payload.Value.Increment(100);
|
||||
}
|
||||
else {
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Failed);
|
||||
_repoStatus.AddOrUpdate(payload.Key, _ => TaskStatusColumn.Statues.Failed
|
||||
, (_, _) => TaskStatusColumn.Statues.Failed);
|
||||
payload.Value.Increment(0);
|
||||
}
|
||||
}
|
||||
|
||||
cts.Cancel();
|
||||
await tAnimate;
|
||||
cts.Dispose();
|
||||
}
|
||||
|
||||
private void StashCurorPos()
|
||||
{
|
||||
_cursorPosBackup = Console.GetCursorPosition();
|
||||
}
|
||||
|
||||
|
||||
public override async Task Run()
|
||||
protected override async Task Core()
|
||||
{
|
||||
// 查找git仓库目录
|
||||
{
|
||||
Console.Write(Str.FindGitReps, Opt.Path);
|
||||
StashCurorPos();
|
||||
var progressBar = new ProgressBarColumn { Width = 10 };
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(progressBar //
|
||||
, new ElapsedTimeColumn() //
|
||||
, new SpinnerColumn() //
|
||||
, new TaskStatusColumn() //
|
||||
, new TaskDescriptionColumn { Alignment = Justify.Left } //
|
||||
)
|
||||
.StartAsync(async ctx => {
|
||||
var taskFinder = ctx.AddTask(string.Format(Str.FindGitReps, Opt.Path)).IsIndeterminate();
|
||||
var paths = Directory.GetDirectories(Opt.Path, ".git" //
|
||||
, new EnumerationOptions //
|
||||
{
|
||||
MaxRecursionDepth = Opt.MaxRecursionDepth
|
||||
, RecurseSubdirectories = true
|
||||
, IgnoreInaccessible = true
|
||||
, AttributesToSkip
|
||||
= FileAttributes.ReparsePoint
|
||||
})
|
||||
.Select(x => Directory.GetParent(x)!.FullName);
|
||||
|
||||
var tAnimate = LoadingAnimate(_cursorPosBackup.x, _cursorPosBackup.y, out var cts);
|
||||
_repoPathList = Directory.GetDirectories(Opt.Path, ".git" //
|
||||
, new EnumerationOptions //
|
||||
{
|
||||
MaxRecursionDepth = Opt.MaxRecursionDepth
|
||||
, RecurseSubdirectories = true
|
||||
, IgnoreInaccessible = true
|
||||
, AttributesToSkip = FileAttributes.ReparsePoint
|
||||
})
|
||||
.Select(x => Directory.GetParent(x)!.FullName)
|
||||
.ToList();
|
||||
cts.Cancel();
|
||||
await tAnimate;
|
||||
cts.Dispose();
|
||||
_repoRsp = new ConcurrentDictionary<string, StringBuilder>();
|
||||
_repoStatus = new ConcurrentDictionary<string, TaskStatusColumn.Statues>();
|
||||
var tasks = new Dictionary<string, ProgressTask>();
|
||||
foreach (var path in paths) {
|
||||
_repoRsp.TryAdd(path, new StringBuilder());
|
||||
_repoStatus.TryAdd(path, default);
|
||||
var task = ctx.AddTask(new DirectoryInfo(path).Name, false).IsIndeterminate();
|
||||
tasks.Add(path, task);
|
||||
}
|
||||
|
||||
taskFinder.IsIndeterminate(false);
|
||||
taskFinder.Increment(100);
|
||||
taskFinder.State.Status(TaskStatusColumn.Statues.Succeed);
|
||||
|
||||
await Parallel.ForEachAsync(tasks, DirHandle);
|
||||
});
|
||||
|
||||
var table = new Table().AddColumn(new TableColumn(Str.Repository) { Width = 50 })
|
||||
.AddColumn(new TableColumn(Str.Command))
|
||||
.AddColumn(new TableColumn(Str.Response) { Width = 50 })
|
||||
.Caption(
|
||||
$"{Str.ZeroCode}: [green]{_repoStatus.Count(x => x.Value == TaskStatusColumn.Statues
|
||||
.Succeed)}[/]/{_repoStatus.Count}");
|
||||
|
||||
|
||||
foreach (var repo in _repoRsp) {
|
||||
var status = _repoStatus[repo.Key].Desc();
|
||||
table.AddRow(status.Replace(_repoStatus[repo.Key].ToString(), new DirectoryInfo(repo.Key).Name), Opt.Args
|
||||
, status.Replace(_repoStatus[repo.Key].ToString(), repo.Value.ToString()));
|
||||
}
|
||||
|
||||
// 打印git仓库目录
|
||||
{
|
||||
Console.WriteLine(Str.Ok);
|
||||
StashCurorPos();
|
||||
|
||||
var i = 0;
|
||||
Console.WriteLine( //
|
||||
string.Join(Environment.NewLine
|
||||
, _repoPathList.Select(
|
||||
x => $"{++i}: {new DirectoryInfo(x).Name.Sub(0, _REP_PATH_LENGTH_LIMIT)}"))
|
||||
//
|
||||
);
|
||||
}
|
||||
|
||||
// 并行执行git命令
|
||||
await Parallel.ForEachAsync(_repoPathList, DirHandle);
|
||||
Console.SetCursorPosition(_cursorPosBackup.x, _cursorPosBackup.y + _repoPathList.Count);
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
}
|
14
src/Git/ProgressTaskStateExtensions.cs
Normal file
14
src/Git/ProgressTaskStateExtensions.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Dot.Git;
|
||||
|
||||
public static class ProgressTaskStateExtensions
|
||||
{
|
||||
public static TaskStatusColumn.Statues Status(this ProgressTaskState me)
|
||||
{
|
||||
return me.Get<TaskStatusColumn.Statues>(nameof(TaskStatusColumn));
|
||||
}
|
||||
|
||||
public static void Status(this ProgressTaskState me, TaskStatusColumn.Statues value)
|
||||
{
|
||||
me.Update<TaskStatusColumn.Statues>(nameof(TaskStatusColumn), _ => value);
|
||||
}
|
||||
}
|
35
src/Git/TaskStatusColumn.cs
Normal file
35
src/Git/TaskStatusColumn.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.ComponentModel;
|
||||
using NSExt.Extensions;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Dot.Git;
|
||||
|
||||
public class TaskStatusColumn : ProgressColumn
|
||||
{
|
||||
public enum Statues : byte
|
||||
{
|
||||
[Description($"[gray]{nameof(Ready)}[/]")]
|
||||
Ready
|
||||
|
||||
, [Description($"[yellow]{nameof(Executing)}[/]")]
|
||||
Executing
|
||||
|
||||
, [Description($"[green]{nameof(Succeed)}[/]")]
|
||||
Succeed
|
||||
|
||||
, [Description($"[red]{nameof(Failed)}[/]")]
|
||||
Failed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the task description.
|
||||
/// </summary>
|
||||
public Justify Alignment { get; set; } = Justify.Right;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||
{
|
||||
var text = task.State.Get<Statues>(nameof(TaskStatusColumn));
|
||||
return new Markup(text.Desc()).Overflow(Overflow.Ellipsis).Justify(Alignment);
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ public sealed class Main : ToolBase<Option>
|
||||
public Main(Option opt) : base(opt) { }
|
||||
|
||||
|
||||
public override Task Run()
|
||||
protected override Task Core()
|
||||
{
|
||||
var guid = System.Guid.NewGuid().ToString();
|
||||
if (Opt.Upper) guid = guid.ToUpper();
|
||||
|
@ -8,7 +8,7 @@ public sealed class Main : ToolBase<Option>
|
||||
{
|
||||
public Main(Option opt) : base(opt) { }
|
||||
|
||||
public override async Task Run()
|
||||
protected override async Task Core()
|
||||
{
|
||||
foreach (var item in NetworkInterface.GetAllNetworkInterfaces()) {
|
||||
if (item.NetworkInterfaceType != NetworkInterfaceType.Ethernet ||
|
||||
|
@ -53,7 +53,7 @@ public class Main : ToolBase<Option>
|
||||
return text.Replace("\\\"", "\"");
|
||||
}
|
||||
|
||||
public override async Task Run()
|
||||
protected override async Task Core()
|
||||
{
|
||||
string result = null;
|
||||
if (Opt.Compress)
|
||||
|
@ -1,190 +1,212 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root" xmlns="">
|
||||
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
|
||||
id="root" xmlns="">
|
||||
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="InputTextIsEmpty" xml:space="preserve">
|
||||
</resheader>
|
||||
<data name="InputTextIsEmpty" xml:space="preserve">
|
||||
<value>The input text is empty</value>
|
||||
</data>
|
||||
<data name="SearchingFile" xml:space="preserve">
|
||||
<data name="SearchingFile" xml:space="preserve">
|
||||
<value>Find files...</value>
|
||||
</data>
|
||||
<data name="PathNotFound" xml:space="preserve">
|
||||
<data name="PathNotFound" xml:space="preserve">
|
||||
<value>The specified path "{0}" does not exist</value>
|
||||
</data>
|
||||
<data name="SearchingFileOK" xml:space="preserve">
|
||||
<data name="SearchingFileOK" xml:space="preserve">
|
||||
<value>{0} files</value>
|
||||
</data>
|
||||
<data name="ShowMessageTemp" xml:space="preserve">
|
||||
<data name="ShowMessageTemp" xml:space="preserve">
|
||||
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
|
||||
</data>
|
||||
<data name="Copied" xml:space="preserve">
|
||||
<data name="Copied" xml:space="preserve">
|
||||
<value>{0}(copied to clipboard)</value>
|
||||
</data>
|
||||
<data name="FileSearchPattern" xml:space="preserve">
|
||||
<data name="FileSearchPattern" xml:space="preserve">
|
||||
<value>File wildcards</value>
|
||||
</data>
|
||||
<data name="FolderPath" xml:space="preserve">
|
||||
<data name="FolderPath" xml:space="preserve">
|
||||
<value>Directory path to be processed</value>
|
||||
</data>
|
||||
<data name="ConvertEndOfLineToLF" xml:space="preserve">
|
||||
<data name="ConvertEndOfLineToLF" xml:space="preserve">
|
||||
<value>Convert newline characters to LF</value>
|
||||
</data>
|
||||
<data name="GuidTool" xml:space="preserve">
|
||||
<data name="GuidTool" xml:space="preserve">
|
||||
<value>GUID tool</value>
|
||||
</data>
|
||||
<data name="UseUppercase" xml:space="preserve">
|
||||
<data name="UseUppercase" xml:space="preserve">
|
||||
<value>Use uppercase output</value>
|
||||
</data>
|
||||
<data name="RandomPasswordGenerator" xml:space="preserve">
|
||||
<data name="RandomPasswordGenerator" xml:space="preserve">
|
||||
<value>Random password generator</value>
|
||||
</data>
|
||||
<data name="PwdLength" xml:space="preserve">
|
||||
<data name="PwdLength" xml:space="preserve">
|
||||
<value>Password length</value>
|
||||
</data>
|
||||
<data name="PwdGenerateTypes" xml:space="preserve">
|
||||
<data name="PwdGenerateTypes" xml:space="preserve">
|
||||
<value>BitSet 1:[0-9],2:[a-z],4:[A-Z],8:[ascii.0x21-0x2F]</value>
|
||||
</data>
|
||||
<data name="RemoveTrailingWhiteSpaces" xml:space="preserve">
|
||||
<data name="RemoveTrailingWhiteSpaces" xml:space="preserve">
|
||||
<value>Remove line breaks and spaces at the end of the file</value>
|
||||
</data>
|
||||
<data name="TrimUtf8Bom" xml:space="preserve">
|
||||
<data name="TrimUtf8Bom" xml:space="preserve">
|
||||
<value>Remove the uf8 bom of the file</value>
|
||||
</data>
|
||||
<data name="TextTobeProcessed" xml:space="preserve">
|
||||
<data name="TextTobeProcessed" xml:space="preserve">
|
||||
<value>Text to be processed (clipboard value is taken by default)</value>
|
||||
</data>
|
||||
<data name="PressAnyKey" xml:space="preserve">
|
||||
<data name="PressAnyKey" xml:space="preserve">
|
||||
<value>Press any key to continue...</value>
|
||||
</data>
|
||||
<data name="NoFileToBeProcessed" xml:space="preserve">
|
||||
<data name="NoFileToBeProcessed" xml:space="preserve">
|
||||
<value>No documents to be processed</value>
|
||||
</data>
|
||||
<data name="TimeoutMillSecs" xml:space="preserve">
|
||||
<data name="TimeoutMillSecs" xml:space="preserve">
|
||||
<value>Timeout for connecting to the NTP server (milliseconds)</value>
|
||||
</data>
|
||||
<data name="SyncToLocalTime" xml:space="preserve">
|
||||
<data name="SyncToLocalTime" xml:space="preserve">
|
||||
<value>Synchronize local time</value>
|
||||
</data>
|
||||
<data name="NtpReceiveDone" xml:space="preserve">
|
||||
<data name="NtpReceiveDone" xml:space="preserve">
|
||||
<value>Success {0}/{1}, the average value of the clock offset of the machine:{2}ms</value>
|
||||
</data>
|
||||
<data name="NtpServerCount" xml:space="preserve">
|
||||
<data name="NtpServerCount" xml:space="preserve">
|
||||
<value>{0}/{1} NTP servers</value>
|
||||
</data>
|
||||
<data name="NtpCalling" xml:space="preserve">
|
||||
<data name="NtpCalling" xml:space="preserve">
|
||||
<value>{0} In communication...</value>
|
||||
</data>
|
||||
<data name="LocalTimeOffset" xml:space="preserve">
|
||||
<data name="LocalTimeOffset" xml:space="preserve">
|
||||
<value>{0}, local clock offset: {1} ms</value>
|
||||
</data>
|
||||
<data name="LocalTimeSyncDone" xml:space="preserve">
|
||||
<data name="LocalTimeSyncDone" xml:space="preserve">
|
||||
<value>Local time has been synchronized</value>
|
||||
</data>
|
||||
<data name="Server" xml:space="preserve">
|
||||
<data name="Server" xml:space="preserve">
|
||||
<value>Server</value>
|
||||
</data>
|
||||
<data name="Status" xml:space="preserve">
|
||||
<data name="Status" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
<data name="LocalClockOffset" xml:space="preserve">
|
||||
<data name="LocalClockOffset" xml:space="preserve">
|
||||
<value>Local clock offset</value>
|
||||
</data>
|
||||
<data name="TimeTool" xml:space="preserve">
|
||||
<data name="TimeTool" xml:space="preserve">
|
||||
<value>Time synchronization tool</value>
|
||||
</data>
|
||||
<data name="TextTool" xml:space="preserve">
|
||||
<data name="TextTool" xml:space="preserve">
|
||||
<value>Text encoding tool</value>
|
||||
</data>
|
||||
<data name="ScreenPixelTool" xml:space="preserve">
|
||||
<data name="ScreenPixelTool" xml:space="preserve">
|
||||
<value>Screen coordinate color selection tool</value>
|
||||
</data>
|
||||
<data name="ClickCopyColor" xml:space="preserve">
|
||||
<data name="ClickCopyColor" xml:space="preserve">
|
||||
<value>Click the left mouse button to copy the colors and coordinates to the clipboard</value>
|
||||
</data>
|
||||
<data name="PublicIP" xml:space="preserve">
|
||||
<data name="PublicIP" xml:space="preserve">
|
||||
<value>Public network ip: </value>
|
||||
</data>
|
||||
<data name="ServerTime" xml:space="preserve">
|
||||
<data name="ServerTime" xml:space="preserve">
|
||||
<value>Synchronize local time</value>
|
||||
</data>
|
||||
<data name="Ip" xml:space="preserve">
|
||||
<data name="Ip" xml:space="preserve">
|
||||
<value>IP tools</value>
|
||||
</data>
|
||||
<data name="KeepSession" xml:space="preserve">
|
||||
<data name="KeepSession" xml:space="preserve">
|
||||
<value>Keep the session after executing the command</value>
|
||||
</data>
|
||||
<data name="NtpServerTime" xml:space="preserve">
|
||||
<data name="NtpClock" xml:space="preserve">
|
||||
<value>NTP server standard clock: {0}</value>
|
||||
</data>
|
||||
<data name="GitTool" xml:space="preserve">
|
||||
<data name="GitTool" xml:space="preserve">
|
||||
<value>Git batch operation tool</value>
|
||||
</data>
|
||||
<data name="GitArgs" xml:space="preserve">
|
||||
<data name="GitArgs" xml:space="preserve">
|
||||
<value>Parameters passed to Git</value>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="FindGitReps" xml:space="preserve">
|
||||
<data name="FindGitReps" xml:space="preserve">
|
||||
<value>Find all git repository directories under "{0}"...</value>
|
||||
</data>
|
||||
<data name="GitOutputEncoding" xml:space="preserve">
|
||||
<data name="GitOutputEncoding" xml:space="preserve">
|
||||
<value>Git output encoding</value>
|
||||
</data>
|
||||
<data name="MaxRecursionDepth" xml:space="preserve">
|
||||
<data name="MaxRecursionDepth" xml:space="preserve">
|
||||
<value>Directory search depth</value>
|
||||
</data>
|
||||
<data name="InvalidJsonString" xml:space="preserve">
|
||||
<data name="InvalidJsonString" xml:space="preserve">
|
||||
<value>Clipboard does not contain correct Json string</value>
|
||||
</data>
|
||||
<data name="Json" xml:space="preserve">
|
||||
<data name="Json" xml:space="preserve">
|
||||
<value>JsonTools</value>
|
||||
</data>
|
||||
<data name="CompressJson" xml:space="preserve">
|
||||
<data name="CompressJson" xml:space="preserve">
|
||||
<value>Compress Json text</value>
|
||||
</data>
|
||||
<data name="FormatJson" xml:space="preserve">
|
||||
<data name="FormatJson" xml:space="preserve">
|
||||
<value>Format JSON text</value>
|
||||
</data>
|
||||
<data name="GeneratorClass" xml:space="preserve">
|
||||
<data name="GeneratorClass" xml:space="preserve">
|
||||
<value>generate entity classes</value>
|
||||
</data>
|
||||
<data name="JsonToString" xml:space="preserve">
|
||||
<data name="JsonToString" xml:space="preserve">
|
||||
<value>Json text escaped into a string</value>
|
||||
</data>
|
||||
<data name="WriteMode" xml:space="preserve">
|
||||
<data name="WriteMode" xml:space="preserve">
|
||||
<value>Enable write mode</value>
|
||||
</data>
|
||||
<data name="ExerciseMode" xml:space="preserve">
|
||||
<data name="ExerciseMode" xml:space="preserve">
|
||||
<value>Read-only mode, the file will not be modified in real time!</value>
|
||||
</data>
|
||||
<data name="Read" xml:space="preserve">
|
||||
<data name="Read" xml:space="preserve">
|
||||
<value>read</value>
|
||||
</data>
|
||||
<data name="Write" xml:space="preserve">
|
||||
<data name="Write" xml:space="preserve">
|
||||
<value>write</value>
|
||||
</data>
|
||||
<data name="Break" xml:space="preserve">
|
||||
<data name="Break" xml:space="preserve">
|
||||
<value>skip</value>
|
||||
</data>
|
||||
<data name="WriteFileStats" xml:space="preserve">
|
||||
<data name="WriteFileStats" xml:space="preserve">
|
||||
<value>Write statistics</value>
|
||||
</data>
|
||||
<data name="LocalClock" xml:space="preserve">
|
||||
<value>local clock</value>
|
||||
</data>
|
||||
<data name="ExcludePathRegexes" xml:space="preserve">
|
||||
<value>Regular expression to exclude paths</value>
|
||||
</data>
|
||||
<data name="Exclude" xml:space="preserve">
|
||||
<value>exclude</value>
|
||||
</data>
|
||||
<data name="Repository" xml:space="preserve">
|
||||
<value>storehouse</value>
|
||||
</data>
|
||||
<data name="Command" xml:space="preserve">
|
||||
<value>Order</value>
|
||||
</data>
|
||||
<data name="Response" xml:space="preserve">
|
||||
<value>response</value>
|
||||
</data>
|
||||
<data name="ZeroCode" xml:space="preserve">
|
||||
<value>Git exit code is zero</value>
|
||||
</data>
|
||||
</root>
|
@ -82,6 +82,9 @@
|
||||
<data name="SyncToLocalTime" xml:space="preserve">
|
||||
<value>同步本机时间</value>
|
||||
</data>
|
||||
<data name="LocalClock" xml:space="preserve">
|
||||
<value>本机时钟</value>
|
||||
</data>
|
||||
<data name="ServerTime" xml:space="preserve">
|
||||
<value>同步本机时间</value>
|
||||
</data>
|
||||
@ -161,8 +164,8 @@
|
||||
<data name="LocalTimeOffset" xml:space="preserve">
|
||||
<value>{0}, 本机时钟偏移: {1} ms</value>
|
||||
</data>
|
||||
<data name="NtpServerTime" xml:space="preserve">
|
||||
<value>NTP 服务器标准时钟: {0}</value>
|
||||
<data name="NtpClock" xml:space="preserve">
|
||||
<value>NTP 标准时钟</value>
|
||||
</data>
|
||||
<data name="LocalTimeSyncDone" xml:space="preserve">
|
||||
<value>本机时间已同步</value>
|
||||
@ -186,7 +189,7 @@
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="FindGitReps" xml:space="preserve">
|
||||
<value>查找 "{0}" 下所有git仓库目录... </value>
|
||||
<value>查找 "{0}" 下所有git仓库目录 </value>
|
||||
</data>
|
||||
<data name="ExerciseMode" xml:space="preserve">
|
||||
<value>只读模式, 不会真实修改文件!</value>
|
||||
@ -205,4 +208,16 @@
|
||||
<data name="WriteFileStats" xml:space="preserve">
|
||||
<value>写入统计</value>
|
||||
</data>
|
||||
<data name="Repository" xml:space="preserve">
|
||||
<value>仓库</value>
|
||||
</data>
|
||||
<data name="Command" xml:space="preserve">
|
||||
<value>命令</value>
|
||||
</data>
|
||||
<data name="Response" xml:space="preserve">
|
||||
<value>响应</value>
|
||||
</data>
|
||||
<data name="ZeroCode" xml:space="preserve">
|
||||
<value>Git退出码为零的</value>
|
||||
</data>
|
||||
</root>
|
@ -14,13 +14,8 @@ Type[] LoadVerbs()
|
||||
async Task Run(object args)
|
||||
{
|
||||
if (args is not OptionBase option) return;
|
||||
|
||||
var tool = ToolsFactory.Create(option);
|
||||
await tool.Run();
|
||||
if (option!.KeepSession) {
|
||||
AnsiConsole.MarkupLine(Str.PressAnyKey);
|
||||
AnsiConsole.Console.Input.ReadKey(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ public sealed class Main : ToolBase<Option>
|
||||
|
||||
public Main(Option opt) : base(opt) { }
|
||||
|
||||
public override Task Run()
|
||||
protected override Task Core()
|
||||
{
|
||||
unsafe {
|
||||
var pSource = stackalloc char[_charTable.Sum(x => x.Length)];
|
||||
|
@ -99,7 +99,7 @@ html-decode: {o.HtmlDecode}
|
||||
Console.WriteLine(outputTemp);
|
||||
}
|
||||
|
||||
public override async Task Run()
|
||||
protected override async Task Core()
|
||||
{
|
||||
if (Opt.Text.NullOrEmpty()) Opt.Text = await ClipboardService.GetTextAsync();
|
||||
if (Opt.Text.NullOrEmpty()) throw new ArgumentException(Str.InputTextIsEmpty);
|
||||
|
230
src/Time/Main.cs
230
src/Time/Main.cs
@ -1,98 +1,37 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Dot.Time;
|
||||
|
||||
public sealed class Main : ToolBase<Option>
|
||||
{
|
||||
private record Server
|
||||
{
|
||||
public int ConsoleRowIndex;
|
||||
public TimeSpan Offset;
|
||||
public ServerStatues Status;
|
||||
}
|
||||
private const int _MAX_DEGREE_OF_PARALLELISM = 10;
|
||||
private const int _NTP_PORT = 123;
|
||||
|
||||
private enum ServerStatues : byte
|
||||
{
|
||||
Ready
|
||||
, Connecting
|
||||
, Succeed
|
||||
, Failed
|
||||
}
|
||||
private readonly string[] _ntpServers = {
|
||||
"ntp.ntsc.ac.cn", "cn.ntp.org.cn", "edu.ntp.org.cn", "cn.pool.ntp.org"
|
||||
, "time.pool.aliyun.com", "time1.aliyun.com", "time2.aliyun.com"
|
||||
, "time3.aliyun.com", "time4.aliyun.com", "time5.aliyun.com"
|
||||
, "time6.aliyun.com", "time7.aliyun.com", "time1.cloud.tencent.com"
|
||||
, "time2.cloud.tencent.com", "time3.cloud.tencent.com"
|
||||
, "time4.cloud.tencent.com", "time5.cloud.tencent.com", "ntp.sjtu.edu.cn"
|
||||
, "ntp.neu.edu.cn", "ntp.bupt.edu.cn", "ntp.shu.edu.cn", "pool.ntp.org"
|
||||
, "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org"
|
||||
, "asia.pool.ntp.org", "time1.google.com", "time2.google.com"
|
||||
, "time3.google.com", "time4.google.com", "time.apple.com"
|
||||
, "time1.apple.com", "time2.apple.com", "time3.apple.com"
|
||||
, "time4.apple.com", "time5.apple.com", "time6.apple.com"
|
||||
, "time7.apple.com", "time.windows.com", "time.nist.gov"
|
||||
, "time-nw.nist.gov", "time-a.nist.gov", "time-b.nist.gov"
|
||||
, "stdtime.gov.hk"
|
||||
};
|
||||
|
||||
private double _offsetAvg;
|
||||
|
||||
|
||||
private const int _MAX_DEGREE_OF_PARALLELISM = 10;
|
||||
private const int _NTP_PORT = 123;
|
||||
private const string _OUTPUT_TEMP = "{0,-30} {1,20} {2,20}";
|
||||
private static readonly object _lock = new();
|
||||
private int _procedCnt;
|
||||
private readonly int _serverCnt;
|
||||
|
||||
private readonly string[] _srvAddr = {
|
||||
"ntp.ntsc.ac.cn", "cn.ntp.org.cn", "edu.ntp.org.cn", "cn.pool.ntp.org"
|
||||
, "time.pool.aliyun.com", "time1.aliyun.com", "time2.aliyun.com"
|
||||
, "time3.aliyun.com", "time4.aliyun.com", "time5.aliyun.com"
|
||||
, "time6.aliyun.com", "time7.aliyun.com", "time1.cloud.tencent.com"
|
||||
, "time2.cloud.tencent.com", "time3.cloud.tencent.com"
|
||||
, "time4.cloud.tencent.com", "time5.cloud.tencent.com", "ntp.sjtu.edu.cn"
|
||||
, "ntp.neu.edu.cn", "ntp.bupt.edu.cn", "ntp.shu.edu.cn", "pool.ntp.org"
|
||||
, "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org"
|
||||
, "asia.pool.ntp.org", "time1.google.com", "time2.google.com"
|
||||
, "time3.google.com", "time4.google.com", "time.apple.com", "time1.apple.com"
|
||||
, "time2.apple.com", "time3.apple.com", "time4.apple.com", "time5.apple.com"
|
||||
, "time6.apple.com", "time7.apple.com", "time.windows.com", "time.nist.gov"
|
||||
, "time-nw.nist.gov", "time-a.nist.gov", "time-b.nist.gov", "stdtime.gov.hk"
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, Server> _srvStatus;
|
||||
private int _successCnt;
|
||||
private int _successCnt;
|
||||
|
||||
|
||||
public Main(Option opt) : base(opt)
|
||||
{
|
||||
_serverCnt = _srvAddr.Length;
|
||||
var i = 0;
|
||||
_srvStatus = _srvAddr.ToDictionary(
|
||||
x => x, _ => new Server { Status = ServerStatues.Ready, ConsoleRowIndex = ++i });
|
||||
}
|
||||
|
||||
private static void ChangeStatus(KeyValuePair<string, Server> server, ServerStatues status
|
||||
, TimeSpan offset = default)
|
||||
{
|
||||
server.Value.Status = status;
|
||||
server.Value.Offset = offset;
|
||||
DrawTextInConsole(0, server.Value.ConsoleRowIndex
|
||||
, string.Format(_OUTPUT_TEMP, server.Key, server.Value.Status
|
||||
, status == ServerStatues.Succeed ? server.Value.Offset : string.Empty));
|
||||
}
|
||||
|
||||
private async Task DrawLoading()
|
||||
{
|
||||
char[] loading = { '-', '\\', '|', '/' };
|
||||
var loadingIndex = 0;
|
||||
while (true) {
|
||||
if (Volatile.Read(ref _procedCnt) == _serverCnt) break;
|
||||
await Task.Delay(100);
|
||||
++loadingIndex;
|
||||
for (var i = 0; i != _serverCnt; ++i)
|
||||
DrawTextInConsole(
|
||||
34, i + 1
|
||||
, _srvStatus[_srvAddr[i]].Status is ServerStatues.Succeed or ServerStatues.Failed
|
||||
? " "
|
||||
: loading[loadingIndex % 4].ToString());
|
||||
}
|
||||
|
||||
Debug.WriteLine(Environment.CurrentManagedThreadId + ":" + DateTime.Now.ToString("O"));
|
||||
}
|
||||
|
||||
private static void DrawTextInConsole(int left, int top, string text)
|
||||
{
|
||||
lock (_lock) {
|
||||
Console.SetCursorPosition(left, top);
|
||||
Console.Write(text);
|
||||
}
|
||||
}
|
||||
public Main(Option opt) : base(opt) { }
|
||||
|
||||
|
||||
private TimeSpan GetNtpOffset(string server)
|
||||
@ -132,32 +71,20 @@ public sealed class Main : ToolBase<Option>
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintTemplate()
|
||||
private ValueTask ServerHandle(KeyValuePair<string, ProgressTask> payload, CancellationToken _)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.CursorVisible = false;
|
||||
var row = //
|
||||
_srvStatus.Select(x //
|
||||
=> string.Format(_OUTPUT_TEMP, x.Key, x.Value.Status
|
||||
, x.Value.Offset == TimeSpan.Zero ? string.Empty : x.Value.Offset));
|
||||
|
||||
|
||||
Console.WriteLine(_OUTPUT_TEMP, Str.Server, Str.Status, Str.LocalClockOffset);
|
||||
Console.WriteLine(string.Join(Environment.NewLine, row));
|
||||
}
|
||||
|
||||
private ValueTask ServerHandle(KeyValuePair<string, Server> server)
|
||||
{
|
||||
ChangeStatus(server, ServerStatues.Connecting);
|
||||
var offset = GetNtpOffset(server.Key);
|
||||
Interlocked.Increment(ref _procedCnt);
|
||||
|
||||
payload.Value.StartTask();
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Connecting);
|
||||
var offset = GetNtpOffset(payload.Key);
|
||||
if (offset == TimeSpan.Zero) {
|
||||
ChangeStatus(server, ServerStatues.Failed);
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Failed);
|
||||
payload.Value.IsIndeterminate(false).Increment(0);
|
||||
}
|
||||
else {
|
||||
payload.Value.State.Status(TaskStatusColumn.Statues.Succeed);
|
||||
payload.Value.State.Result(offset);
|
||||
Interlocked.Increment(ref _successCnt);
|
||||
ChangeStatus(server, ServerStatues.Succeed, offset);
|
||||
payload.Value.IsIndeterminate(false).Increment(100);
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
@ -179,46 +106,67 @@ public sealed class Main : ToolBase<Option>
|
||||
Win32.SetLocalTime(timeToSet);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "AccessToDisposedClosure")]
|
||||
|
||||
protected override async Task Core()
|
||||
{
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(new TaskDescriptionColumn() //
|
||||
, new ProgressBarColumn() //
|
||||
, new ElapsedTimeColumn() //
|
||||
, new SpinnerColumn() //
|
||||
, new TaskStatusColumn() //
|
||||
, new TaskResultColumn())
|
||||
.StartAsync(async ctx => {
|
||||
var tasks = new Dictionary<string, ProgressTask>();
|
||||
foreach (var server in _ntpServers) {
|
||||
var t = ctx.AddTask(server, false).IsIndeterminate();
|
||||
tasks.Add(server, t);
|
||||
}
|
||||
|
||||
await Parallel.ForEachAsync(
|
||||
tasks, new ParallelOptions { MaxDegreeOfParallelism = _MAX_DEGREE_OF_PARALLELISM }
|
||||
, ServerHandle);
|
||||
|
||||
_offsetAvg = tasks.Where(x => x.Value.State.Status() == TaskStatusColumn.Statues.Succeed)
|
||||
.Average(x => x.Value.State.Result().TotalMilliseconds);
|
||||
});
|
||||
|
||||
AnsiConsole.MarkupLine(Str.NtpReceiveDone, $"[green]{_successCnt}[/]", _ntpServers.Length
|
||||
, $"[yellow]{_offsetAvg:f2}[/]");
|
||||
|
||||
|
||||
if (Opt.Sync) {
|
||||
SetSysteTime(DateTime.Now.AddMilliseconds(-_offsetAvg));
|
||||
AnsiConsole.MarkupLine($"[green]{Str.LocalTimeSyncDone}[/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Run()
|
||||
{
|
||||
PrintTemplate();
|
||||
var tLoading = DrawLoading();
|
||||
await Core();
|
||||
if (Opt.KeepSession) {
|
||||
var table = new Table().HideHeaders()
|
||||
.AddColumn(new TableColumn(string.Empty))
|
||||
.AddColumn(new TableColumn(string.Empty))
|
||||
.Caption(Str.PressAnyKey)
|
||||
.AddRow(Str.NtpClock, DateTime.Now.AddMilliseconds(-_offsetAvg).ToString("O"))
|
||||
.AddRow(Str.LocalClock, DateTime.Now.ToString("O"));
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var task = AnsiConsole.Live(table)
|
||||
.StartAsync(async ctx => {
|
||||
while (!cts.IsCancellationRequested) {
|
||||
ctx.UpdateTarget(
|
||||
table.UpdateCell(
|
||||
0, 1, DateTime.Now.AddMilliseconds(-_offsetAvg).ToString("O"))
|
||||
.UpdateCell(1, 1, DateTime.Now.ToString("O")));
|
||||
await Task.Delay(100);
|
||||
}
|
||||
});
|
||||
|
||||
await Parallel.ForEachAsync(_srvStatus
|
||||
, new ParallelOptions { MaxDegreeOfParallelism = _MAX_DEGREE_OF_PARALLELISM }
|
||||
, (server, _) => ServerHandle(server));
|
||||
|
||||
await tLoading;
|
||||
|
||||
|
||||
var avgOffset = TimeSpan.FromTicks((long)_srvStatus //
|
||||
.Where(x => x.Value.Status == ServerStatues.Succeed)
|
||||
.Average(x => x.Value.Offset.Ticks));
|
||||
|
||||
Console.SetCursorPosition(0, _serverCnt + 1);
|
||||
Console.WriteLine(Str.NtpReceiveDone, _successCnt, _serverCnt, avgOffset.TotalMilliseconds);
|
||||
|
||||
if (!Opt.Sync) {
|
||||
if (!Opt.KeepSession) return;
|
||||
|
||||
var waitObj = new ManualResetEvent(false);
|
||||
var _ = Task.Run(async () => {
|
||||
var top = Console.GetCursorPosition().Top;
|
||||
while (true) {
|
||||
Console.SetCursorPosition(0, top);
|
||||
Console.Write(Str.NtpServerTime, (DateTime.Now - avgOffset).ToString("O"));
|
||||
waitObj.Set();
|
||||
await Task.Delay(100);
|
||||
}
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
});
|
||||
waitObj.WaitOne();
|
||||
return;
|
||||
await AnsiConsole.Console.Input.ReadKeyAsync(true, cts.Token);
|
||||
cts.Cancel();
|
||||
await task;
|
||||
}
|
||||
|
||||
SetSysteTime(DateTime.Now - avgOffset);
|
||||
Console.WriteLine(Str.LocalTimeSyncDone);
|
||||
}
|
||||
}
|
24
src/Time/ProgressTaskStateExtensions.cs
Normal file
24
src/Time/ProgressTaskStateExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Dot.Time;
|
||||
|
||||
public static class ProgressTaskStateExtensions
|
||||
{
|
||||
public static TimeSpan Result(this ProgressTaskState me)
|
||||
{
|
||||
return me.Get<TimeSpan>(nameof(TaskResultColumn));
|
||||
}
|
||||
|
||||
public static void Result(this ProgressTaskState me, TimeSpan value)
|
||||
{
|
||||
me.Update<TimeSpan>(nameof(TaskResultColumn), _ => value);
|
||||
}
|
||||
|
||||
public static TaskStatusColumn.Statues Status(this ProgressTaskState me)
|
||||
{
|
||||
return me.Get<TaskStatusColumn.Statues>(nameof(TaskStatusColumn));
|
||||
}
|
||||
|
||||
public static void Status(this ProgressTaskState me, TaskStatusColumn.Statues value)
|
||||
{
|
||||
me.Update<TaskStatusColumn.Statues>(nameof(TaskStatusColumn), _ => value);
|
||||
}
|
||||
}
|
18
src/Time/TaskResultColumn.cs
Normal file
18
src/Time/TaskResultColumn.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Dot.Time;
|
||||
|
||||
public class TaskResultColumn : ProgressColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the task description.
|
||||
/// </summary>
|
||||
public Justify Alignment { get; set; } = Justify.Right;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||
{
|
||||
var text = task.State.Get<TimeSpan>(nameof(TaskResultColumn));
|
||||
return new Markup(text.ToString()).Overflow(Overflow.Ellipsis).Justify(Alignment);
|
||||
}
|
||||
}
|
35
src/Time/TaskStatusColumn.cs
Normal file
35
src/Time/TaskStatusColumn.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.ComponentModel;
|
||||
using NSExt.Extensions;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Dot.Time;
|
||||
|
||||
public class TaskStatusColumn : ProgressColumn
|
||||
{
|
||||
public enum Statues : byte
|
||||
{
|
||||
[Description($"[gray]{nameof(Ready)}[/]")]
|
||||
Ready
|
||||
|
||||
, [Description($"[yellow]{nameof(Connecting)}[/]")]
|
||||
Connecting
|
||||
|
||||
, [Description($"[green]{nameof(Succeed)}[/]")]
|
||||
Succeed
|
||||
|
||||
, [Description($"[red]{nameof(Failed)}[/]")]
|
||||
Failed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the task description.
|
||||
/// </summary>
|
||||
public Justify Alignment { get; set; } = Justify.Right;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||
{
|
||||
var text = task.State.Get<Statues>(nameof(TaskStatusColumn));
|
||||
return new Markup(text.Desc()).Overflow(Overflow.Ellipsis).Justify(Alignment);
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task Core();
|
||||
|
||||
|
||||
protected static Task LoadingAnimate(int x, int y, out CancellationTokenSource cts)
|
||||
{
|
||||
@ -50,6 +52,12 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public abstract Task Run();
|
||||
public virtual async Task Run()
|
||||
{
|
||||
await Core();
|
||||
if (Opt.KeepSession) {
|
||||
AnsiConsole.MarkupLine(Str.PressAnyKey);
|
||||
AnsiConsole.Console.Input.ReadKey(true);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user