<feat> + git批量操作工具

This commit is contained in:
nsnail 2022-12-06 17:49:49 +08:00
parent b3665aba40
commit c89c4ffbfb
9 changed files with 173 additions and 15 deletions

89
src/Git/Main.cs Normal file
View File

@ -0,0 +1,89 @@
using System.Diagnostics;
using System.Text;
using NSExt.Extensions;
namespace Dot.Git;
public class Main : ToolBase<Option>
{
private const int _POS_Y_MSG = 74;
private const int _POST_Y_LOADING = 70;
private const int _REP_MAX_LENGTH = 32;
private (int x, int y) _cursorInitPos;
private List<string> _dirList;
private Encoding _encGbk;
public Main(Option opt) : base(opt) { }
private async ValueTask DirHandle(string dir, CancellationToken cancelToken)
{
var index = _dirList.FindIndex(x => x == dir);
var tAnimate = LoadingAnimate(_POST_Y_LOADING, _cursorInitPos.y + index, out var cts);
void Write(object sender, DataReceivedEventArgs e)
{
if (e.Data is null) return;
var msg = Encoding.UTF8.GetString(_encGbk.GetBytes(e.Data));
ConcurrentWrite(_POS_Y_MSG, _cursorInitPos.y + index, new string(' ', Console.WindowWidth - _POS_Y_MSG));
ConcurrentWrite(_POS_Y_MSG, _cursorInitPos.y + index, msg);
}
var gitStartInfo = new ProcessStartInfo {
CreateNoWindow = true
, WorkingDirectory = dir
, FileName = "git"
, Arguments = Opt.Args
, UseShellExecute = false
, RedirectStandardOutput = true
, RedirectStandardError = true
};
using var p = Process.Start(gitStartInfo);
p.OutputDataReceived += Write;
p.ErrorDataReceived += Write;
p.BeginOutputReadLine();
p.BeginErrorReadLine();
await p.WaitForExitAsync();
cts.Cancel();
await tAnimate;
cts.Dispose();
}
public override async Task Run()
{
if (!Directory.Exists(Opt.Path))
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
_encGbk = Encoding.GetEncoding("gbk");
Console.Write(Str.FindGitReps, Opt.Path);
_cursorInitPos = Console.GetCursorPosition();
var tAnimate = LoadingAnimate(_cursorInitPos.x, _cursorInitPos.y, out var cts);
_dirList = Directory.GetDirectories(Opt.Path, ".git", SearchOption.AllDirectories)
.Select(x => Directory.GetParent(x)!.FullName)
.ToList();
cts.Cancel();
await tAnimate;
cts.Dispose();
Console.WriteLine(Str.Ok);
_cursorInitPos = Console.GetCursorPosition();
var i = 0;
Console.WriteLine(string.Join(Environment.NewLine
, _dirList.Select(
x => $"{++i}: {new DirectoryInfo(x).Name.Sub(0, _REP_MAX_LENGTH)}")));
await Parallel.ForEachAsync(_dirList, DirHandle);
Console.SetCursorPosition(_cursorInitPos.x, _cursorInitPos.y + _dirList.Count);
}
}

11
src/Git/Option.cs Normal file
View File

@ -0,0 +1,11 @@
namespace Dot.Git;
[Verb("git", HelpText = nameof(Str.GitTool), ResourceType = typeof(Str))]
public class Option : OptionBase
{
[Option('a', "args", HelpText = nameof(Str.GitArgs), Default = "status", ResourceType = typeof(Str))]
public string Args { get; set; }
[Value(0, HelpText = nameof(Str.FolderPath), Default = ".", ResourceType = typeof(Str))]
public string Path { get; set; }
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root" xmlns=""> id="root" xmlns="">
@ -30,7 +30,7 @@
<value>The specified path "{0}" does not exist</value> <value>The specified path "{0}" does not exist</value>
</data> </data>
<data name="SearchingFileOK" xml:space="preserve"> <data name="SearchingFileOK" xml:space="preserve">
<value>Find files...OK</value> <value>{0} files</value>
</data> </data>
<data name="ShowMessageTemp" xml:space="preserve"> <data name="ShowMessageTemp" xml:space="preserve">
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value> <value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
@ -137,4 +137,16 @@
<data name="NtpServerTime" xml:space="preserve"> <data name="NtpServerTime" xml:space="preserve">
<value>NTP server standard clock: {0}</value> <value>NTP server standard clock: {0}</value>
</data> </data>
<data name="GitTool" xml:space="preserve">
<value>Git batch operation tool</value>
</data>
<data name="GitArgs" xml:space="preserve">
<value>Parameters passed to Git</value>
</data>
<data name="Ok" xml:space="preserve">
<value>OK</value>
</data>
<data name="FindGitReps" xml:space="preserve">
<value>Find all git repository directories under "{0}"...</value>
</data>
</root> </root>

View File

@ -81,9 +81,15 @@
<data name="GuidTool" xml:space="preserve"> <data name="GuidTool" xml:space="preserve">
<value>GUID工具</value> <value>GUID工具</value>
</data> </data>
<data name="GitTool" xml:space="preserve">
<value>Git批量操作工具</value>
</data>
<data name="UseUppercase" xml:space="preserve"> <data name="UseUppercase" xml:space="preserve">
<value>使用大写输出</value> <value>使用大写输出</value>
</data> </data>
<data name="GitArgs" xml:space="preserve">
<value>传递给Git的参数</value>
</data>
<data name="RandomPasswordGenerator" xml:space="preserve"> <data name="RandomPasswordGenerator" xml:space="preserve">
<value>随机密码生成器</value> <value>随机密码生成器</value>
</data> </data>
@ -145,4 +151,10 @@
<data name="PublicIP" xml:space="preserve"> <data name="PublicIP" xml:space="preserve">
<value>Public network ip... </value> <value>Public network ip... </value>
</data> </data>
<data name="Ok" xml:space="preserve">
<value>OK</value>
</data>
<data name="FindGitReps" xml:space="preserve">
<value>查找 "{0}" 下所有git仓库目录... </value>
</data>
</root> </root>

View File

@ -1,7 +1,7 @@
<#@ template language="C#" #> <#@ template language="C#" #>
<#@ import namespace="System.Xml" #>
<#@ assembly name="System.Xml" #> <#@ assembly name="System.Xml" #>
<#@ output encoding="utf-8" extension="Designer.cs" #> <#@ output encoding="utf-8" extension="Designer.cs" #>
<#@ import namespace="System.Xml" #>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -69,7 +69,7 @@ namespace Dot.Lang {
var xml = new XmlDocument(); var xml = new XmlDocument();
xml.Load("Str.resx"); xml.Load("Str.resx");
foreach (XmlNode data in xml.SelectNodes("//root/data")) { foreach (XmlNode data in xml.SelectNodes("//root/data")) {
#> #>
/// <summary> /// <summary>
/// <#= data.SelectSingleNode("value").InnerText #> /// <#= data.SelectSingleNode("value").InnerText #>
/// </summary> /// </summary>

View File

@ -12,8 +12,9 @@ Type[] LoadVerbs()
async Task Run(object args) async Task Run(object args)
{ {
var option = args as OptionBase; if (args is not OptionBase option) return;
var tool = ToolsFactory.Create(option);
var tool = ToolsFactory.Create(option);
await tool.Run(); await tool.Run();
if (option!.KeepSession) { if (option!.KeepSession) {
Console.WriteLine(); Console.WriteLine();

View File

@ -2,6 +2,8 @@ namespace Dot;
public abstract class ToolBase<TOption> : ITool where TOption : OptionBase public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
{ {
private static readonly object _lockObj = new();
protected readonly ProgressBarOptions // protected readonly ProgressBarOptions //
DefaultProgressBarOptions = new() { DefaultProgressBarOptions = new() {
MessageEncodingName = "utf-8" MessageEncodingName = "utf-8"
@ -20,6 +22,15 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
Opt = opt; Opt = opt;
} }
protected static void ConcurrentWrite(int x, int y, string text)
{
lock (_lockObj) {
Console.SetCursorPosition(x, y);
Console.Write(text);
}
}
protected static IEnumerable<string> EnumerateFiles(string path, string searchPattern) protected static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
{ {
var fileList = Directory var fileList = Directory
@ -33,6 +44,28 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
return fileList; return fileList;
} }
protected static Task LoadingAnimate(int x, int y, out CancellationTokenSource cts)
{
char[] animateChars = { '-', '\\', '|', '/' };
long counter = 0;
cts = new CancellationTokenSource();
var cancelToken = cts.Token;
return Task.Run(async () => {
for (;;) {
if (cancelToken.IsCancellationRequested) {
ConcurrentWrite(x, y, @" ");
return;
}
ConcurrentWrite(x, y, animateChars[counter++ % 4].ToString());
await Task.Delay(100);
}
});
}
protected static void MoveFile(string source, string dest) protected static void MoveFile(string source, string dest)
{ {
try { try {
@ -65,6 +98,5 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
return fsr; return fsr;
} }
public abstract Task Run(); public abstract Task Run();
} }

View File

@ -16,6 +16,7 @@ public static class ToolsFactory
, Time.Option o => new Time.Main(o) , Time.Option o => new Time.Main(o)
, Color.Option o => new Color.Main(o) , Color.Option o => new Color.Main(o)
, IP.Option o => new IP.Main(o) , IP.Option o => new IP.Main(o)
, Git.Option o => new Git.Main(o)
, _ => throw new ArgumentOutOfRangeException(nameof(option)) , _ => throw new ArgumentOutOfRangeException(nameof(option))
}; };
} }

View File

@ -24,10 +24,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1"/>
<PackageReference Include="NSExt" Version="1.0.6" /> <PackageReference Include="NSExt" Version="1.0.6"/>
<PackageReference Include="ShellProgressBar" Version="5.2.0" /> <PackageReference Include="ShellProgressBar" Version="5.2.0"/>
<PackageReference Include="TextCopy" Version="6.2.0" /> <PackageReference Include="TextCopy" Version="6.2.0"/>
</ItemGroup> </ItemGroup>
@ -37,7 +37,7 @@
<LastGenOutput>Str.Designer.cs</LastGenOutput> <LastGenOutput>Str.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Lang\Str.Designer.cs"> <Compile Update="Lang\Str.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
@ -48,10 +48,10 @@
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<ItemGroup Condition="!Exists('Lang\Str.Designer.cs')"> <ItemGroup Condition="!Exists('Lang\Str.Designer.cs')">
<Compile Include="Lang\Str.Designer.cs" /> <Compile Include="Lang\Str.Designer.cs"/>
</ItemGroup> </ItemGroup>
<Exec Command="dotnet tool restore" /> <Exec Command="dotnet tool restore"/>
<Exec WorkingDirectory="$(ProjectDir)\Lang" Command="dotnet t4 Str.tt" /> <Exec WorkingDirectory="$(ProjectDir)\Lang" Command="dotnet t4 Str.tt"/>
</Target> </Target>
</Project> </Project>