Implement Logger, Caching, Args (#2)
This adds basic testing, the logger, caching and an commandline argument parser Reviewed-on: #2 closes #1
This commit is contained in:
parent
78183fcc9e
commit
50abe7a88b
6 changed files with 521 additions and 0 deletions
6
BeefProj.toml
Normal file
6
BeefProj.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
FileVersion = 1
|
||||
|
||||
[Project]
|
||||
Name = "Common"
|
||||
TargetType = "BeefLib"
|
||||
StartupObject = "Common.Program"
|
5
BeefSpace.toml
Normal file
5
BeefSpace.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
FileVersion = 1
|
||||
Projects = {Common = {Path = "."}}
|
||||
|
||||
[Workspace]
|
||||
StartupProject = "Common"
|
204
src/Args.bf
Normal file
204
src/Args.bf
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
Common - Booklordofthedings - 2025 - Args
|
||||
A parser for commandline arguments or things similar to that.
|
||||
There is one specific syntax that is used and cannot be changed,
|
||||
this syntax is similar to most other programs but slightly more limited.
|
||||
|
||||
verb verb --Name Value -flag -flag -NotAFlag value
|
||||
|
||||
Every item that doesnt start with a '-' before the first '-' is considered the verb.
|
||||
clone/pull etc...
|
||||
A single command might have multiple verbs
|
||||
|
||||
'-' '--' can be used interchangeably and either indicate a flag or a value
|
||||
This depends on wether the next entry also starts with a '-'
|
||||
If it doesnt, then its considered a value pair
|
||||
Otherwise its just a flag thats set to true
|
||||
|
||||
--value key1 key2 --flag
|
||||
One value might have multiple keys
|
||||
*/
|
||||
|
||||
namespace Common;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
class Args
|
||||
{
|
||||
private List<String> _args = new .(10) ~ DeleteContainerAndItems!(_);
|
||||
private List<StringView> _verbs = new .(10) ~ delete _;
|
||||
private List<StringView> _flags = new .(10) ~ delete _;
|
||||
private Dictionary<StringView, List<StringView>> _values = new .(10) ~ DeleteDictionaryAndValues!(_);
|
||||
|
||||
public this<T>(T args) where T : IEnumerable<String>, concrete
|
||||
{
|
||||
for (var i in args)
|
||||
_args.Add(new .(i));
|
||||
_ParseArguments();
|
||||
}
|
||||
|
||||
public this(StringView args)
|
||||
{
|
||||
loop:for (var part in args.Split(' '))
|
||||
{
|
||||
if (part.IsEmpty)
|
||||
continue;
|
||||
|
||||
/// "content" <- cut out the quotes and use it
|
||||
if (part.StartsWith('"') && part.EndsWith('"') && !part.EndsWith("\\\""))
|
||||
{
|
||||
_args.Add(new .(part.Substring(1, part.Length - 2)));
|
||||
continue loop;
|
||||
}
|
||||
|
||||
/// "sadas dsfsdfsd gfdfgdfgdfg dasdas" <- Find the end and use it
|
||||
if (part.StartsWith('"'))
|
||||
{
|
||||
int startIndex = @part.Pos;
|
||||
int endIndex = -1;
|
||||
for (var p in @part)
|
||||
{
|
||||
if (p.EndsWith('"') && !p.EndsWith("\\\""))
|
||||
endIndex = @p.Pos + p.Length;
|
||||
}
|
||||
|
||||
if (endIndex < 0)
|
||||
endIndex = args.Length + 1; //There is no " so we need to move one further
|
||||
|
||||
_args.Add(new .(args.Substring(startIndex + 1, endIndex - 2 - startIndex)));
|
||||
|
||||
continue loop;
|
||||
}
|
||||
|
||||
_args.Add(new .(part));
|
||||
}
|
||||
|
||||
_ParseArguments();
|
||||
}
|
||||
|
||||
/// Parse the list of arguments thats stored in _args to make further calculations faster
|
||||
private void _ParseArguments()
|
||||
{
|
||||
bool verbSearch = true;
|
||||
StringView currentValue = "";
|
||||
for (var i < _args.Count)
|
||||
{
|
||||
if (verbSearch)
|
||||
{
|
||||
if (_args[i].StartsWith('-'))
|
||||
{
|
||||
verbSearch = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_verbs.Add(_args[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_args[i].StartsWith('-'))
|
||||
{
|
||||
_values[currentValue].Add(_args[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( //If there are more entries but they start with - or if there are none, we handle this as a flag
|
||||
(i + 1 < _args.Count && _args[i + 1].StartsWith('-'))
|
||||
|| !(i + 1 < _args.Count))
|
||||
{
|
||||
_flags.Add(_args[i].StartsWith("--") ? _args[i].Substring(2) : _args[i].Substring(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
//Otherwise its a value
|
||||
currentValue = _args[i].StartsWith("--") ? _args[i].Substring(2) : _args[i].Substring(1);
|
||||
if (_values.ContainsKey(currentValue))
|
||||
delete _values.GetAndRemove(currentValue).Value.value;
|
||||
_values.Add(currentValue, new .(10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Retrieve the argument at the given index or return an error if it doesnt exist
|
||||
/// @param index The n'th argument
|
||||
public Result<StringView> GetArgument(uint32 index)
|
||||
{
|
||||
if (index < _args.Count)
|
||||
return .Ok(_args[index]);
|
||||
return .Err;
|
||||
}
|
||||
|
||||
/// Retrieve nth verb or return an error if it doesnt exist
|
||||
/// @param index The n'th argument
|
||||
public Result<StringView> GetVerb(uint32 index = 0)
|
||||
{
|
||||
if (index >= _verbs.Count)
|
||||
return .Err;
|
||||
return _verbs[index];
|
||||
}
|
||||
|
||||
/// Retrieve all of the verbs
|
||||
public void GetVerbs(List<StringView> verbs)
|
||||
{
|
||||
for (var v in _verbs)
|
||||
verbs.Add(v);
|
||||
}
|
||||
|
||||
/// Retrieve all of the flags
|
||||
public void GetFlags(List<StringView> flags)
|
||||
{
|
||||
for (var f in _flags)
|
||||
flags.Add(f);
|
||||
}
|
||||
|
||||
/// Check wether the arguments have a certain sets of flags
|
||||
public bool HasFlag(params Span<StringView> name)
|
||||
{
|
||||
for (var f in _flags)
|
||||
for (var check in name)
|
||||
if (f == check)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check wether the argument list has contains a value of any amount of given names
|
||||
public bool HasValue(params Span<StringView> flags)
|
||||
{
|
||||
for (var flag in flags)
|
||||
if (_values.ContainsKey(flag))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Adds all values from the name parameters into the value list
|
||||
public void GetValues(List<StringView> values, params Span<StringView> name)
|
||||
{
|
||||
for (var flag in name)
|
||||
if (_values.ContainsKey(flag))
|
||||
for (var value in _values[flag])
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
/// Returns the first entry of the given value or errors
|
||||
public Result<StringView> GetValue(params Span<StringView> name)
|
||||
{
|
||||
for (var flag in name)
|
||||
if (_values.ContainsKey(flag))
|
||||
return .Ok(_values[flag].Front);
|
||||
|
||||
return .Err;
|
||||
}
|
||||
|
||||
/// This works like GetValue but can remove a conditional statement from the calling code
|
||||
public StringView GetValueOrDefault(StringView dfault, params Span<StringView> values)
|
||||
{
|
||||
for (var flag in values)
|
||||
if (_values.ContainsKey(flag))
|
||||
return _values[flag].Front;
|
||||
|
||||
return dfault;
|
||||
}
|
||||
}
|
||||
|
||||
|
87
src/Caching.bf
Normal file
87
src/Caching.bf
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Common - Booklordofthedings - 2025 - Caching
|
||||
Caching is used to avoid recalculating large slow operations.
|
||||
A key for an operation is generated and the result is saved under that key
|
||||
if a future key matches an existing result that result will be returned
|
||||
instead of a recalculation.
|
||||
*/
|
||||
|
||||
namespace Common.Caching;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
interface ICacheProvider
|
||||
{
|
||||
public void Cache(Variant ownedValue, StringView key, DateTime timeout = DateTime.UtcNow.AddYears(2));
|
||||
|
||||
public void ClearCache();
|
||||
|
||||
public Result<Variant> GetCachedValue(StringView key);
|
||||
|
||||
public bool HasCachedValue(StringView key);
|
||||
}
|
||||
|
||||
interface ICacheProvider<T>
|
||||
{
|
||||
public void Cache(T ownedValue, StringView key, DateTime timeout = DateTime.UtcNow.AddYears(2));
|
||||
|
||||
public void ClearCache();
|
||||
|
||||
public Result<T> GetCachedValue(StringView key);
|
||||
|
||||
public bool HasCachedValue(StringView key);
|
||||
}
|
||||
|
||||
class CommonCacheProvider : ICacheProvider, ICacheProvider<Variant>
|
||||
{
|
||||
private int32 _nextIndex = 0;
|
||||
private List<(Variant, DateTime)> _items ~ delete _;
|
||||
private Dictionary<String, int64> _indexes = new .() ~ DeleteDictionaryAndKeys!(_);
|
||||
|
||||
|
||||
public ~this()
|
||||
{
|
||||
for(var i in _items)
|
||||
i.0.Dispose();
|
||||
}
|
||||
|
||||
public void Cache(Variant ownedValue, StringView key, DateTime timeout = DateTime.UtcNow.AddYears(2))
|
||||
{
|
||||
if(_indexes.GetValue(scope .(key)) case .Ok(let index))
|
||||
{
|
||||
_items[index].0.Dispose();
|
||||
_items[index].0 = ownedValue;
|
||||
_items[index].1 = timeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.Add((ownedValue, timeout));
|
||||
_indexes.Add(new .(key), _items.Count-1);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
for(var i in _indexes)
|
||||
delete i.key;
|
||||
|
||||
for(var i in _items)
|
||||
i.0.Dispose();
|
||||
}
|
||||
|
||||
public Result<Variant> GetCachedValue(StringView key)
|
||||
{
|
||||
if(!_indexes.ContainsKeyAlt<StringView>(key))
|
||||
return .Err;
|
||||
|
||||
return .Ok(_items[_indexes.GetValue(scope .(key))].0);
|
||||
}
|
||||
|
||||
public bool HasCachedValue(StringView key)
|
||||
{
|
||||
if(_indexes.ContainsKeyAlt<StringView>(key))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
153
src/Logging.bf
Normal file
153
src/Logging.bf
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
Common - Booklordofthedings - 2025 - Logging
|
||||
ILogger aims to provide a generic logger implementation, that allows library creators to use logging as normal,
|
||||
without a hard dependency to a specific logging library.
|
||||
Both of the enums (LogLevel, LogSetting) can be extended by libraries and logger implementations.
|
||||
!IMPORTANT!: Never use a switch without a default clause for these enums, since that breaks functionality
|
||||
if they are extended.
|
||||
*/
|
||||
|
||||
namespace Common.Logging;
|
||||
|
||||
using System;
|
||||
|
||||
interface ILogger
|
||||
{
|
||||
public void Log(LogLevel level, StringView message); //Log a message at the specified log level
|
||||
|
||||
/*
|
||||
These are all shorthands for logging with a specific LogLevel.
|
||||
These should not be extended because it might break stuff.
|
||||
They are here because they are slightly faster to use than a normal log
|
||||
*/
|
||||
public void Trace(StringView message);
|
||||
public void Debug(StringView message);
|
||||
public void Info(StringView message);
|
||||
public void Warn(StringView message);
|
||||
public void Error(StringView message);
|
||||
public void Fatal(StringView message);
|
||||
|
||||
//Self explanatory
|
||||
public LogLevel GetLogLevel();
|
||||
public Result<void> SetLogLevel(LogLevel level);
|
||||
|
||||
//I use string as a value here, because it limits error potential to parsing
|
||||
//while having multiple types as inputs here could potentially create more issues
|
||||
//Also code libraries should preferably not touch the settings, these are for endusers
|
||||
public Result<void> GetSettingValue(LogSetting setting, String value);
|
||||
public Result<void> SetSettingValue(LogSetting setting, StringView value);
|
||||
}
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
/*
|
||||
LogLevel exists to distinguish log events by their severity and
|
||||
to enable users to block certain logs from occuring.
|
||||
Debug and Trace should not output any log events in release mode.
|
||||
*/
|
||||
Trace = 0,
|
||||
Debug = 10,
|
||||
Info = 20,
|
||||
Warn = 30,
|
||||
Error = 40,
|
||||
Fatal = 50
|
||||
}
|
||||
|
||||
enum LogSetting
|
||||
{
|
||||
DoConsoleLog,
|
||||
DoFileLog
|
||||
}
|
||||
|
||||
class CommonLogger : ILogger
|
||||
{
|
||||
private LogLevel _logLevel = .Debug;
|
||||
|
||||
public void Log(LogLevel level, StringView message)
|
||||
{
|
||||
if (!(_logLevel.Underlying <= level.Underlying))
|
||||
return;
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case .Trace:
|
||||
Console.WriteLine(scope $"[trc]:{message}");
|
||||
case .Debug:
|
||||
Console.WriteLine(scope $"[dbg]:{message}");
|
||||
case .Info:
|
||||
Console.WriteLine(scope $"[inf]:{message}");
|
||||
case .Warn:
|
||||
Console.WriteLine(scope $"[wrn]:{message}");
|
||||
case .Error:
|
||||
Console.WriteLine(scope $"[err]:{message}");
|
||||
case .Fatal:
|
||||
Console.WriteLine(scope $"[ftl]:{message}");
|
||||
default:
|
||||
Console.WriteLine($"[{level}]:{message}");
|
||||
}
|
||||
}
|
||||
|
||||
#if !(DEBUG || TEST)
|
||||
[SkipCall]
|
||||
#endif
|
||||
public void Trace(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying >= LogLevel.Trace.Underlying)
|
||||
Console.WriteLine(scope $"[trc]:{message}");
|
||||
}
|
||||
|
||||
#if !(DEBUG || TEST)
|
||||
[SkipCall]
|
||||
#endif
|
||||
public void Debug(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying <= LogLevel.Debug.Underlying)
|
||||
Console.WriteLine(scope $"[dbg]:{message}");
|
||||
}
|
||||
|
||||
public void Info(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying <= LogLevel.Info.Underlying)
|
||||
Console.WriteLine(scope $"[ifo]:{message}");
|
||||
}
|
||||
|
||||
public void Warn(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying <= LogLevel.Warn.Underlying)
|
||||
Console.WriteLine(scope $"[wrn]:{message}");
|
||||
}
|
||||
|
||||
public void Error(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying <= LogLevel.Error.Underlying)
|
||||
Console.WriteLine(scope $"[err]:{message}");
|
||||
}
|
||||
|
||||
public void Fatal(StringView message)
|
||||
{
|
||||
if (_logLevel.Underlying <= LogLevel.Fatal.Underlying)
|
||||
Console.WriteLine(scope $"[ftl]:{message}");
|
||||
}
|
||||
|
||||
public LogLevel GetLogLevel()
|
||||
{
|
||||
return _logLevel;
|
||||
}
|
||||
|
||||
public Result<void> SetLogLevel(LogLevel level)
|
||||
{
|
||||
_logLevel = level;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> GetSettingValue(LogSetting setting, String value)
|
||||
{
|
||||
return .Err;
|
||||
}
|
||||
|
||||
public Result<void> SetSettingValue(LogSetting setting, StringView value)
|
||||
{
|
||||
return .Err;
|
||||
}
|
||||
|
||||
}
|
66
src/Tests/Args.bf
Normal file
66
src/Tests/Args.bf
Normal file
|
@ -0,0 +1,66 @@
|
|||
namespace Common.Tests;
|
||||
|
||||
using System;
|
||||
|
||||
static
|
||||
{
|
||||
[Test(Name = "Common.Args.SingleStringParsing")]
|
||||
public static void StringParsing()
|
||||
{
|
||||
|
||||
//Testing the functionality of the single string parsing
|
||||
TestStringParsing("");
|
||||
|
||||
TestStringParsing(
|
||||
"some default testing",
|
||||
"some", "default", "testing"
|
||||
);
|
||||
|
||||
TestStringParsing(
|
||||
"\"Single argument in quotes \" ",
|
||||
"Single argument in quotes "
|
||||
);
|
||||
|
||||
|
||||
TestStringParsing(
|
||||
"\"SomeArgument\"",
|
||||
"SomeArgument"
|
||||
);
|
||||
|
||||
TestStringParsing(
|
||||
"\"it even works when its not closed",
|
||||
"it even works when its not closed"
|
||||
);
|
||||
|
||||
TestStringParsing(
|
||||
"mixed things \"Also work\"",
|
||||
"mixed", "things", "Also work"
|
||||
);
|
||||
}
|
||||
|
||||
private static void TestStringParsing(StringView input, params Span<StringView> outputs)
|
||||
{
|
||||
Args a = scope .(input);
|
||||
Test.Assert(a.[Friend]_args.Count == outputs.Length);
|
||||
for (int i < outputs.Length)
|
||||
Test.Assert(a.[Friend]_args[i] == outputs[i]);
|
||||
}
|
||||
|
||||
[Test(Name = "Common.Args.Categorization")]
|
||||
public static void ArgumentCategorization()
|
||||
{
|
||||
//Here we test for wether any given input is actually processed into the correct thing
|
||||
|
||||
Args a = scope .(
|
||||
scope String[](
|
||||
"verb", "verb_2", "verb_3", "-flag", "--another_flag", "-even_more_flags", "-value", "value1", "value2", "--next_value", "okey"
|
||||
));
|
||||
|
||||
Test.Assert(a.[Friend]_args.Count == 11);
|
||||
Test.Assert(a.[Friend]_verbs.Count == 3);
|
||||
Test.Assert(a.[Friend]_values.Count == 2);
|
||||
Test.Assert(a.[Friend]_flags.Count == 3);
|
||||
|
||||
Test.Assert(a.[Friend]_values["value"].Count == 2);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue