Skip to content

Commit 242c56d

Browse files
committed
[+] 请AI给我搓了个CLI,暂时没有仔细调。
1 parent 364ce12 commit 242c56d

3 files changed

Lines changed: 187 additions & 6 deletions

File tree

MuConvert.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<PrivateAssets>all</PrivateAssets>
3737
</PackageReference>
3838
<PackageReference Include="Rationals" Version="2.3.0" />
39+
<PackageReference Include="System.CommandLine" Version="2.0.5" />
3940
</ItemGroup>
4041

4142
<ItemGroup>

Program.cs

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,186 @@
1-
// See https://aka.ms/new-console-template for more information
2-
1+
using System.CommandLine;
2+
using System.Text;
3+
using MuConvert.generator;
4+
using MuConvert.maidata;
5+
using MuConvert.parser.simai;
36
using MuConvert.utils;
47

5-
Console.WriteLine("Hello, World!");
6-
Console.WriteLine(Utils.AppVersion);
8+
namespace MuConvert;
9+
10+
internal static class Program
11+
{
12+
private static int Main(string[] args)
13+
{
14+
var root = BuildRootCommand();
15+
try
16+
{
17+
return root.Parse(args).Invoke();
18+
}
19+
catch (NotImplementedException ex)
20+
{
21+
Console.Error.WriteLine(ex.Message);
22+
return 1;
23+
}
24+
catch (ConversionException ex)
25+
{
26+
PrintAlerts(ex.Alerts, "转换失败:");
27+
Console.Error.WriteLine("转换失败!报错详见如上。您可以通过 https://github.com/MuNet-OSS/MuConvert/issues 反馈问题。");
28+
return 1;
29+
}
30+
catch (Exception ex) when (ex is ArgumentException or IOException or UnauthorizedAccessException)
31+
{
32+
Console.Error.WriteLine(ex.Message);
33+
return 1;
34+
}
35+
}
36+
37+
private static Command BuildRootCommand()
38+
{
39+
var root = new RootCommand($"MuConvert {Utils.AppVersion} — simai / maidata → MA2")
40+
{
41+
Description = "将 .txt 格式的 simai 单谱或 maidata 转为 MA2,输出与输入同目录的 lv_N.ma2。"
42+
};
43+
44+
var levelsOption = new Option<string?>("--levels", "-l")
45+
{
46+
Description = "仅转换指定难度(maidata 的 inote 编号),逗号分隔;省略则全部。纯 simai 单谱不可使用本选项。",
47+
HelpName = "N[,N...]"
48+
};
49+
50+
var inputArgument = new Argument<string>("inputfile")
51+
{
52+
Description = "输入 .txt(单谱 simai 或 maidata)",
53+
Arity = ArgumentArity.ExactlyOne
54+
};
55+
56+
root.Options.Add(levelsOption);
57+
root.Arguments.Add(inputArgument);
58+
59+
root.SetAction(parseResult =>
60+
{
61+
var inputPath = parseResult.GetValue(inputArgument)
62+
?? throw new InvalidOperationException("缺少参数 inputfile。");
63+
var levelsRaw = parseResult.GetValue(levelsOption);
64+
RunConvert(inputPath, levelsRaw);
65+
});
66+
67+
return root;
68+
}
69+
70+
private static void RunConvert(string inputPath, string? levelsRaw)
71+
{
72+
var levelFilter = string.IsNullOrWhiteSpace(levelsRaw)
73+
? null
74+
: ParseLevelList(levelsRaw);
75+
76+
var ext = Path.GetExtension(inputPath);
77+
if (string.Equals(ext, ".ma2", StringComparison.OrdinalIgnoreCase))
78+
throw new NotImplementedException("从 .ma2 输入的转换尚未实现。");
79+
80+
if (!string.Equals(ext, ".txt", StringComparison.OrdinalIgnoreCase))
81+
throw new ArgumentException($"不支持的输入扩展名「{ext}」。目前仅支持 .txt(simai / maidata)。");
82+
83+
if (!File.Exists(inputPath))
84+
throw new ArgumentException($"找不到文件: {inputPath}");
85+
86+
var inputDir = Path.GetDirectoryName(Path.GetFullPath(inputPath))!;
87+
var text = File.ReadAllText(inputPath, Encoding.UTF8);
88+
89+
if (LooksLikeMaidata(text))
90+
ConvertMaidata(text, inputDir, levelFilter);
91+
else
92+
ConvertPlainSimai(text, inputDir, levelFilter);
93+
}
94+
95+
private static HashSet<int> ParseLevelList(string s)
96+
{
97+
var parts = s.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
98+
if (parts.Length == 0)
99+
throw new ArgumentException("-l / --levels 的难度列表不能为空。");
100+
101+
var set = new HashSet<int>();
102+
foreach (var p in parts)
103+
{
104+
if (!int.TryParse(p, out var id) || id <= 0)
105+
throw new ArgumentException($"无效的难度编号: 「{p}」。");
106+
set.Add(id);
107+
}
108+
return set;
109+
}
110+
111+
private static bool LooksLikeMaidata(string text) =>
112+
text.Contains("&inote_", StringComparison.Ordinal);
113+
114+
/// <summary>
115+
/// lv_N 字段:空或仅含数字、点、加号 → 非宴谱;否则(含汉字等)→ 宴谱。
116+
/// </summary>
117+
private static bool IsUtageFromLevelString(string? level)
118+
{
119+
if (string.IsNullOrWhiteSpace(level))
120+
return false;
121+
foreach (var c in level.Trim())
122+
{
123+
if (char.IsDigit(c) || c is '.' or '+')
124+
continue;
125+
return true;
126+
}
127+
return false;
128+
}
129+
130+
private static void PrintAlerts(IReadOnlyList<Alert> alerts, string? header = null)
131+
{
132+
if (alerts.Count == 0)
133+
return;
134+
if (header != null)
135+
Console.Error.WriteLine(header);
136+
foreach (var a in alerts)
137+
Console.Error.WriteLine(a.ToString());
138+
}
139+
140+
private static void ConvertMaidata(string text, string outputDir, HashSet<int>? levelFilter)
141+
{
142+
var maidata = new Maidata(text);
143+
var ids = maidata.Levels.Keys.OrderBy(k => k).ToList();
144+
if (ids.Count == 0)
145+
throw new ArgumentException("maidata 中未找到任何 &inote_* 谱面。");
146+
147+
var selected = levelFilter == null
148+
? ids
149+
: ids.Where(id => levelFilter.Contains(id)).ToList();
150+
151+
if (selected.Count == 0)
152+
throw new ArgumentException("-l / --levels 指定的难度在文件中均不存在。");
153+
154+
foreach (var id in selected)
155+
{
156+
var chartInfo = maidata.Levels[id];
157+
var bigTouch = id is 2 or 3;
158+
var isUtage = IsUtageFromLevelString(chartInfo.Level);
159+
var ma2 = SimaiToMa2(chartInfo.Inote, maidata.ClockCount, bigTouch, isUtage);
160+
var outPath = Path.Combine(outputDir, $"lv_{id}.ma2");
161+
File.WriteAllText(outPath, ma2, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
162+
}
163+
}
164+
165+
private static void ConvertPlainSimai(string text, string outputDir, HashSet<int>? levelFilter)
166+
{
167+
if (levelFilter != null)
168+
throw new ArgumentException("纯 simai 单谱(非 maidata)不能使用 -l / --levels。");
169+
170+
const int outputLevel = 0;
171+
var ma2 = SimaiToMa2(text);
172+
var outPath = Path.Combine(outputDir, $"lv_{outputLevel}.ma2");
173+
File.WriteAllText(outPath, ma2, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
174+
}
175+
176+
private static string SimaiToMa2(string inote, int clockCount=4, bool bigTouch=false, bool isUtage=false)
177+
{
178+
var (chart, parseAlerts) = new SimaiParser(bigTouch, isUtage).Parse(inote);
179+
var (ma2, genAlerts) = new MA2Generator(clockCount).Generate(chart);
180+
var combined = new List<Alert>(parseAlerts.Count + genAlerts.Count);
181+
combined.AddRange(parseAlerts);
182+
combined.AddRange(genAlerts);
183+
PrintAlerts(combined);
184+
return ma2;
185+
}
186+
}

utils/Error.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ public override string ToString()
5959

6060
public class ConversionException : Exception
6161
{
62-
public ConversionException(List<Alert> alerts)
62+
public ConversionException(List<Alert> alerts): base(alerts.LastOrDefault()?.ToString())
6363
{
6464
Alerts = alerts;
6565
}
6666

67-
public ConversionException(List<Alert> alerts, Exception? innerException) : base("", innerException)
67+
public ConversionException(List<Alert> alerts, Exception? innerException) : base(alerts.LastOrDefault()?.ToString(), innerException)
6868
{
6969
Alerts = alerts;
7070
}

0 commit comments

Comments
 (0)