Skip to content

Commit b06b3ce

Browse files
committed
Submit CLIMediaParser files
This is a simpler version of the `advcli_player` branch, I wanted to submit it instead of this one but going around Beef bugs was being a fucking time blackhole so I gave up, maybe I will complete the `advcli_player` in the future.
1 parent 525e3d9 commit b06b3ce

6 files changed

Lines changed: 531 additions & 0 deletions

File tree

CLIMediaPlayer/BeefProj.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FileVersion = 1
2+
Dependencies = {corlib = "*", BeefBass = "*"}
3+
4+
[Project]
5+
Name = "CLIMediaPlayer"
6+
StartupObject = "CLIMediaPlayer.Program"
7+
8+
[[Project.DistinctOptions]]
9+
Filter = "System.String"

CLIMediaPlayer/src/CLI/CLI.bf

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.Collections;
3+
using System.Reflection;
4+
5+
namespace CLIMediaPlayer.CLI
6+
{
7+
abstract class CLI<T>
8+
{
9+
public static bool CLIRunning = false;
10+
11+
public static void ShowCommands()
12+
{
13+
int maxCmdStrLength = 0;
14+
List<(String, String)> commandList = scope .();
15+
16+
for (let method in typeof(T).GetMethods(.Static | .Public))
17+
{
18+
CommandAttribute attrib = ?;
19+
if (!(method.GetCustomAttribute<CommandAttribute>() case .Ok(out attrib)))
20+
continue;
21+
22+
String str = scope:: .();
23+
24+
str.Append(attrib.mCommand);
25+
str.Append(" ");
26+
27+
for (let argAttrib in method.GetCustomAttributes<CommandArgAttribute>())
28+
{
29+
if (argAttrib.mOptional)
30+
str.AppendF("[{0}] ", argAttrib.mArgName);
31+
else
32+
str.AppendF("<{0}> ", argAttrib.mArgName);
33+
}
34+
35+
if (maxCmdStrLength < str.Length)
36+
maxCmdStrLength = str.Length;
37+
38+
commandList.Add((str, attrib.mDesc));
39+
}
40+
41+
for (let (cmdStr, desc) in commandList)
42+
{
43+
Console.Write(cmdStr..PadRight(maxCmdStrLength + 1));
44+
Console.WriteLine(desc);
45+
}
46+
}
47+
48+
public static void Run()
49+
{
50+
CLIRunning = true;
51+
52+
runLoop: while (CLIRunning)
53+
{
54+
Console.Write("> ");
55+
56+
String cmd = scope .();
57+
if (Console.ReadLine(cmd) case .Err)
58+
{
59+
CLIRunning = false;
60+
break;
61+
}
62+
63+
cmd.Trim();
64+
var argEnumerator = cmd.Split(' ');
65+
66+
StringView argName;
67+
if (!(argEnumerator.GetNext() case .Ok(out argName)))
68+
continue;
69+
70+
for (let method in typeof(T).GetMethods(.Static | .Public))
71+
mLoop: {
72+
CommandAttribute attrib = ?;
73+
if (!(method.GetCustomAttribute<CommandAttribute>() case .Ok(out attrib)))
74+
continue;
75+
76+
if (attrib.mCommand != argName)
77+
continue;
78+
79+
int argCount = 0;
80+
for (let argAttrib in method.GetCustomAttributes<CommandArgAttribute>())
81+
argCount++;
82+
83+
int argIdx = 0;
84+
Object[] argArray = scope Object[argCount];
85+
86+
for (let argAttrib in method.GetCustomAttributes<CommandArgAttribute>())
87+
{
88+
StringView argStr = .();
89+
if (!(argEnumerator.GetNext() case .Ok(out argStr)) && !argAttrib.mOptional)
90+
{
91+
Console.WriteLine("Not enough arguments to run command '{}', expected {} more.", attrib.mCommand, argCount - argIdx);
92+
continue runLoop;
93+
}
94+
95+
Type argType = argAttrib.mArgType;
96+
bool argNullable = argType.IsNullable;
97+
if (argNullable)
98+
argType = ((SpecializedGenericType)argType).GetGenericArg(0);
99+
100+
switch (argType)
101+
{
102+
case typeof(TimeSpan):
103+
if (!argStr.IsEmpty)
104+
{
105+
TimeSpan argTS;
106+
if (!(TimeSpan.Parse(argStr) case .Ok(out argTS)))
107+
{
108+
Console.WriteLine("Time is in invalid format.");
109+
continue runLoop;
110+
}
111+
112+
argArray[argIdx] = scope:mLoop box argTS;
113+
}
114+
else if (!argNullable)
115+
Runtime.FatalError("Argument is optional but isn't nullable!");
116+
case typeof(bool):
117+
if (!argStr.IsEmpty)
118+
{
119+
if (argStr == "y" || argStr == "n")
120+
argArray[argIdx] = scope:mLoop box argStr == "y";
121+
else
122+
{
123+
Console.WriteLine("Boolean is in invalid format.");
124+
continue runLoop;
125+
}
126+
}
127+
else if (!argNullable)
128+
Runtime.FatalError("Argument is optional but isn't nullable!");
129+
case typeof(double):
130+
if (!argStr.IsEmpty)
131+
{
132+
double argDouble;
133+
if (!(double.Parse(argStr) case .Ok(out argDouble)))
134+
{
135+
Console.WriteLine("Number is in invalid format.");
136+
continue runLoop;
137+
}
138+
139+
argArray[argIdx] = scope:mLoop box argDouble;
140+
}
141+
else if (!argNullable)
142+
Runtime.FatalError("Argument is optional but isn't nullable!");
143+
case typeof(String):
144+
String fullArgStr = scope:mLoop .();
145+
argArray[argIdx] = fullArgStr;
146+
147+
if (argStr.StartsWith('"') && argStr.EndsWith('"'))
148+
argStr = argStr[1...^2];
149+
150+
if (argStr.StartsWith('"'))
151+
{
152+
fullArgStr.Set(argStr[1...]);
153+
fullArgStr.Append(' ');
154+
155+
while (true)
156+
{
157+
if (!(argEnumerator.GetNext() case .Ok(out argStr)))
158+
{
159+
Console.WriteLine("Unterminated quoted string.");
160+
continue runLoop;
161+
}
162+
163+
if (argStr.EndsWith('"'))
164+
{
165+
fullArgStr.Append(argStr[...^2]);
166+
break;
167+
}
168+
169+
fullArgStr.Append(argStr);
170+
fullArgStr.Append(' ');
171+
}
172+
}
173+
else
174+
{
175+
fullArgStr.Set(argStr);
176+
}
177+
}
178+
179+
argIdx++;
180+
}
181+
182+
if (method.Invoke(null, params argArray) case .Ok(var val))
183+
val.Dispose();
184+
185+
continue runLoop;
186+
}
187+
188+
if (!argName.IsEmpty)
189+
Console.WriteLine("Unknown command '{}'.", argName);
190+
}
191+
}
192+
}
193+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace CLIMediaPlayer.CLI
4+
{
5+
[AttributeUsage(.Method, .AlwaysIncludeTarget | .ReflectAttribute)]
6+
struct CommandArgAttribute : Attribute
7+
{
8+
public String mArgName;
9+
public Type mArgType;
10+
public bool mOptional;
11+
12+
public this(String argName, Type argType, bool optional = false)
13+
{
14+
mArgName = argName;
15+
mArgType = argType;
16+
mOptional = optional;
17+
}
18+
}
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace CLIMediaPlayer.CLI
4+
{
5+
[AttributeUsage(.Method, .AlwaysIncludeTarget | .ReflectAttribute)]
6+
struct CommandAttribute : Attribute
7+
{
8+
public String mCommand;
9+
public String mDesc;
10+
11+
public this(String cmd, String desc)
12+
{
13+
mCommand = cmd;
14+
mDesc = desc;
15+
}
16+
}
17+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace System
2+
{
3+
extension TimeSpan
4+
{
5+
// Very simple TimeSpan.Parse, better than nothing
6+
public static Result<TimeSpan> Parse(StringView str)
7+
{
8+
var msSeparator = str.Split('.');
9+
var components = Try!(msSeparator.GetNext()).Split(':');
10+
11+
mixin ParseComponent(var component)
12+
{
13+
int32 parsedComponent;
14+
if (!(int32.Parse(component) case .Ok(out parsedComponent)))
15+
return .Err;
16+
parsedComponent
17+
}
18+
19+
let hours = ParseComponent!(components.GetNext());
20+
let minutes = ParseComponent!(Try!(components.GetNext()));
21+
22+
if (!components.HasMore)
23+
{
24+
if (msSeparator.HasMore)
25+
return .Err;
26+
return TimeSpan(hours, minutes, 0);
27+
}
28+
29+
let seconds = ParseComponent!(components.GetNext());
30+
31+
if (!components.HasMore)
32+
{
33+
int32 milliseconds = 0;
34+
if (msSeparator.HasMore)
35+
milliseconds = ParseComponent!(msSeparator.GetNext());
36+
return TimeSpan(0, hours, minutes, seconds, milliseconds);
37+
}
38+
39+
return .Err;
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)