Support for command line arguments (#155)

This commit is contained in:
2023-06-18 18:02:02 +08:00
parent aabe6664b4
commit b2b656b6cb
5 changed files with 244 additions and 8 deletions

View File

@ -282,9 +282,11 @@
</Compile> </Compile>
<Compile Include="Utils\ArtifactUtils.cs" /> <Compile Include="Utils\ArtifactUtils.cs" />
<Compile Include="Utils\Common.cs" /> <Compile Include="Utils\Common.cs" />
<Compile Include="Utils\GuiRedirect.cs" />
<Compile Include="Utils\HttpHelper.cs" /> <Compile Include="Utils\HttpHelper.cs" />
<Compile Include="Utils\Logger.cs" /> <Compile Include="Utils\Logger.cs" />
<Compile Include="Utils\GithubHelper.cs" /> <Compile Include="Utils\GithubHelper.cs" />
<Compile Include="Utils\ToggleParser.cs" />
<Compile Include="Utils\UIUtil.cs" /> <Compile Include="Utils\UIUtil.cs" />
<EmbeddedResource Include="Forms\FormActivityEditor.en-US.resx"> <EmbeddedResource Include="Forms\FormActivityEditor.en-US.resx">
<DependentUpon>FormActivityEditor.cs</DependentUpon> <DependentUpon>FormActivityEditor.cs</DependentUpon>

View File

@ -18,11 +18,13 @@
**/ **/
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using GrasscutterTools.OpenCommand;
using GrasscutterTools.Properties; using GrasscutterTools.Properties;
using GrasscutterTools.Utils; using GrasscutterTools.Utils;
@ -63,8 +65,12 @@ namespace GrasscutterTools
/// 应用程序的主入口点。 /// 应用程序的主入口点。
/// </summary> /// </summary>
[STAThread] [STAThread]
private static void Main() private static int Main(string[] args)
{ {
var result = HandleCommandLine(args);
if (result != -1)
return result;
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
@ -91,8 +97,129 @@ namespace GrasscutterTools
Application.Run(new Forms.FormMain()); Application.Run(new Forms.FormMain());
Logger.I(TAG, "Program end."); Logger.I(TAG, "Program end.");
return 0;
} }
#region - -
/// <summary>
/// 处理命令行参数并返回处理结果
/// </summary>
/// <param name="args">命令行参数</param>
/// <returns>返回-1表示继续启动应用程序。返回其它值表示退出应用并将该值作为返回结果。</returns>
private static int HandleCommandLine(string[] args)
{
var parser = new ToggleParser(args);
if (parser.IsEmpty) return -1;
try
{
GuiRedirect.Redirect();
// 是否启动日志
if (parser.HasToggle("debug") || parser.HasToggle("log"))
Logger.IsSaveLogs = true;
if (parser.HasToggle("v") || parser.HasToggle("version"))
{
Console.WriteLine("v" + Common.AppVersion.ToString(3));
return 0;
}
if (parser.HasToggle("h") || parser.HasToggle("help") || parser.HasToggle("?"))
{
Console.WriteLine("Usages:");
Console.WriteLine(" GcTools.exe -help");
Console.WriteLine(" GcTools.exe -version");
Console.WriteLine(" GcTools.exe -c \"cmd arg\"");
Console.WriteLine(" GcTools.exe -c \"cmd1 arg\" && GcTools -c \"cmd2 arg1 arg2\"");
Console.WriteLine(" GcTools.exe -host http://127.0.0.1:443 -token 123456 -c \"cmd1 arg1 arg2 | cmd2 | cmd3 arg\"");
return 0;
}
// 服务器地址
var host = parser.GetToggleValueOrDefault("host", Settings.Default.Host);
// 服务器令牌
var token = parser.GetToggleValueOrDefault("token", Settings.Default.TokenCache);
if (Settings.Default.Host != host || Settings.Default.TokenCache != token)
{
Settings.Default.Host = host;
Settings.Default.TokenCache = token;
Settings.Default.Save();
}
#if DEBUG
Logger.I(TAG, $"Host: {Settings.Default.Host} Token: {Settings.Default.TokenCache}");
#endif
// UID
//Settings.Default.RemoteUid = decimal.Parse(parser.GetToggleValueOrDefault("uid", Settings.Default.RemoteUid.ToString()));
if (!string.IsNullOrEmpty(Settings.Default.Host) && !string.IsNullOrEmpty(Settings.Default.TokenCache))
{
Common.OC = new OpenCommandAPI(Settings.Default.Host, Settings.Default.TokenCache);
}
// 解析并执行命令
var cmd = parser.GetToggleValueOrDefault("c", string.Empty);
if (string.IsNullOrEmpty(cmd)) cmd = parser.GetToggleValueOrDefault("command", string.Empty);
if (!string.IsNullOrEmpty(cmd))
{
return RunCommand(cmd) ? 0 : 1;
}
}
catch (Exception ex)
{
Logger.E(TAG, "Parse command failed!", ex);
}
return -1;
}
/// <summary>
/// 执行命令
/// </summary>
/// <param name="commands">GC命令由|分割多条命令</param>
/// <returns>返回是否执行成功</returns>
private static bool RunCommand(string commands)
{
if (Common.OC == null || !Common.OC.CanInvoke)
{
Console.WriteLine(Resources.RequireOpenCommandTip);
Logger.E(TAG, Resources.RequireOpenCommandTip);
return false;
}
try
{
foreach (var cmd in commands.Split('|').Select(FormatCommand))
{
var msg = Common.OC.Invoke(cmd).Result;
Console.WriteLine(string.IsNullOrEmpty(msg) ? "OK" : msg);
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
Logger.E(TAG, "RunCommand Error:", ex);
return false;
}
}
/// <summary>
/// 格式化命令
/// (去除收尾空白,替换换行)
/// </summary>
/// <param name="raw">原始输入</param>
/// <returns>格式化后可执行命令</returns>
private static string FormatCommand(string raw)
{
return raw.Trim().Replace("\\r", "\r").Replace("\\n", "\n");
}
#endregion
#region - - #region - -
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)

View File

@ -0,0 +1,64 @@
using System;
using System.Runtime.InteropServices;
namespace GrasscutterTools.Utils
{
/// <summary>
/// <see cref="https://stackoverflow.com/a/17534263"/>
/// </summary>
public class GuiRedirect
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr handle);
//[DllImport("kernel32.dll", SetLastError = true)]
//[return: MarshalAs(UnmanagedType.Bool)]
//static extern bool AllocConsole();
private enum StandardHandle : uint
{
Input = unchecked((uint)-10),
Output = unchecked((uint)-11),
Error = unchecked((uint)-12)
}
private enum FileType : uint
{
Unknown = 0x0000,
Disk = 0x0001,
Char = 0x0002,
Pipe = 0x0003
}
private static bool IsRedirected(IntPtr handle)
{
FileType fileType = GetFileType(handle);
return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
}
public static void Redirect()
{
if (IsRedirected(GetStdHandle(StandardHandle.Output)))
{
var initialiseOut = Console.Out;
}
bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
if (errorRedirected)
{
var initialiseError = Console.Error;
}
AttachConsole(-1);
if (!errorRedirected)
SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
}
}
}

View File

@ -25,16 +25,17 @@ namespace GrasscutterTools.Utils
{ {
public static class Logger public static class Logger
{ {
public static bool IsSaveLogs = false;
private static readonly string LogPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), $"GcTools-{DateTime.Now:MMdd}.log"); private static readonly string LogPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), $"GcTools-{DateTime.Now:MMdd}.log");
private static void Write(string message) private static void Write(string message)
{ {
#if DEBUG if (IsSaveLogs)
Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}"); {
#else Console.WriteLine($"{DateTime.Now:mm:ss.fff} {message}");
// Test log File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}");
//File.AppendAllText(LogPath, $"{DateTime.Now:mm:ss.fff} {message}{Environment.NewLine}"); }
#endif
} }
private static void Write(string level, string tag, string message) => Write($"<{level}:{tag}> {message}"); private static void Write(string level, string tag, string message) => Write($"<{level}:{tag}> {message}");

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace GrasscutterTools.Utils
{
/// <summary>
/// Simple command line toggles parser:
/// - toggles are identified with (any number of) '-' prefixes
/// - toggle can be with or without associated value
/// - toggles are case-insensitive
///
/// <example>--toggle_without_value -toggle value</example>
/// <see cref="https://gist.github.com/jchapuis/64b5adf9d0f3062e6a72dded110a6028"/>
/// </summary>
internal class ToggleParser
{
private readonly Dictionary<string, string> toggles;
public ToggleParser(string[] args)
{
toggles =
args.Zip(args.Skip(1).Concat(new[] { string.Empty }), (first, second) => new { first, second })
.Where(pair => IsToggle(pair.first))
.ToDictionary(pair => RemovePrefix(pair.first).ToLowerInvariant(), g => IsToggle(g.second) ? string.Empty : g.second);
}
private static string RemovePrefix(string toggle)
=> new string(toggle.SkipWhile(c => c == '-').ToArray());
private static bool IsToggle(string arg)
=> arg.StartsWith("-", StringComparison.InvariantCulture);
public bool HasToggle(string toggle)
=> toggles.ContainsKey(toggle.ToLowerInvariant());
public string GetToggleValueOrDefault(string toggle, string defaultValue)
=> toggles.TryGetValue(toggle.ToLowerInvariant(), out var value) ? value : defaultValue;
public bool IsEmpty => toggles.Count == 0;
}
}