From 099e3d5e925e287710489e08ec23ff68263fd796 Mon Sep 17 00:00:00 2001 From: Maxwell Ruben Date: Wed, 18 Mar 2026 22:54:06 -0500 Subject: [PATCH] Initial commit --- dscanner.ini | 3 + dub.sdl | 3 + dub.selections.json | 12 ++++ source/thirdworld/common/config.d | 41 ++++++++++++++ source/thirdworld/common/log.d | 92 +++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 dscanner.ini create mode 100644 dub.sdl create mode 100644 dub.selections.json create mode 100644 source/thirdworld/common/config.d create mode 100644 source/thirdworld/common/log.d diff --git a/dscanner.ini b/dscanner.ini new file mode 100644 index 0000000..42cb03e --- /dev/null +++ b/dscanner.ini @@ -0,0 +1,3 @@ +[analysis.config.StaticAnalysisConfig] +number_style_check="disabled" +long_line_check="disabled" diff --git a/dub.sdl b/dub.sdl new file mode 100644 index 0000000..915e940 --- /dev/null +++ b/dub.sdl @@ -0,0 +1,3 @@ +name "thirdworld-common" +targetType "sourceLibrary" +dependency "vibe-core" version="~>2.14.0" diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..703b62e --- /dev/null +++ b/dub.selections.json @@ -0,0 +1,12 @@ +{ + "fileVersion": 1, + "versions": { + "during": "0.3.0", + "eventcore": "0.9.39", + "silly": "1.1.1", + "stdx-allocator": "2.77.5", + "taggedalgebraic": "1.0.1", + "vibe-container": "1.7.1", + "vibe-core": "2.14.0" + } +} diff --git a/source/thirdworld/common/config.d b/source/thirdworld/common/config.d new file mode 100644 index 0000000..522059a --- /dev/null +++ b/source/thirdworld/common/config.d @@ -0,0 +1,41 @@ +module thirdworld.common.config; + +import common.log; + +import std.file : readText, exists, isFile, write; +import std.json; +import std.path : absolutePath; + +JSONValue structToJSON(T)(T s) { + JSONValue json; + foreach (member; __traits(allMembers, T)) { + json[member] = JSONValue(__traits(getMember, s, member)); + } + return json; +} + +void createConfigFile(T)(string path) { + path = absolutePath(path); + if (exists(path) && isFile(path)) { + return; + } + + logInfo("No config file found, creating default config file at '%s'", path); + + T config; + auto json = structToJSON(config); + write(path, json.toPrettyString()); +} + +T configFromFile(T)(string path) { + string text = readText(path); + JSONValue json = parseJSON(text); + + T config; + + foreach (member; __traits(allMembers, T)) { + config[member] = json[member]; + } + + return config; +} \ No newline at end of file diff --git a/source/thirdworld/common/log.d b/source/thirdworld/common/log.d new file mode 100644 index 0000000..ee08573 --- /dev/null +++ b/source/thirdworld/common/log.d @@ -0,0 +1,92 @@ +module thirdworld.common.log; + +import std.stdio : File, stdout, stderr; +import vibe.core.log; + +public import vibe.core.log : logCritical, logDebug, logDebugV, logDiagnostic, + logError, logException, logFatal, logInfo, + logTrace, logWarn, setLogLevel, LogLevel; + +shared static this() { + auto logger = cast(shared) new ThirdworldLogger; + deregisterAllLoggers(); + registerLogger(logger); + logger.minLevel = LogLevel.info; +} + +final class ThirdworldLogger : Logger { + private { + File infoFile; + File diagFile; + File curFile; + } + + this(File info, File diag) { + infoFile = info; + diagFile = diag; + } + + this(string filename) { + auto f = File(filename, "ab"); + this(f, f); + } + + this() { + infoFile = stdout; + diagFile = stderr; + } + + override void beginLine(ref LogLine message) @trusted { + string level; + final switch (message.level) { + case LogLevel.trace: level = "Trace"; curFile = diagFile; break; + case LogLevel.debugV: level = "Debug"; curFile = diagFile; break; + case LogLevel.debug_: level = "Debug"; curFile = diagFile; break; + case LogLevel.diagnostic: level = "Diagnostic"; curFile = diagFile; break; + case LogLevel.info: level = "Info"; curFile = infoFile; break; + case LogLevel.warn: level = "Warn"; curFile = diagFile; break; + case LogLevel.error: level = "Error"; curFile = diagFile; break; + case LogLevel.critical: level = "Critical"; curFile = diagFile; break; + case LogLevel.fatal: level = "Fatal"; curFile = diagFile; break; + case LogLevel.none: assert(false); + } + + auto dst = curFile.lockingTextWriter; + final switch (message.level) { + case LogLevel.trace: dst.put("\x1b[49;38;5;243m"); break; + case LogLevel.debugV: dst.put("\x1b[49;38;5;245m"); break; + case LogLevel.debug_: dst.put("\x1b[49;38;5;180m"); break; + case LogLevel.diagnostic: dst.put("\x1b[49;38;5;143m"); break; + case LogLevel.info: dst.put("\x1b[49;38;5;29m"); break; + case LogLevel.warn: dst.put("\x1b[49;38;5;220m"); break; + case LogLevel.error: dst.put("\x1b[49;38;5;9m"); break; + case LogLevel.critical: dst.put("\x1b[41;38;5;15m"); break; + case LogLevel.fatal: dst.put("\x1b[48;5;9;30m"); break; + case LogLevel.none: assert(false); + } + + auto time = message.time; + auto msecs = time.fracSecs.total!"msecs"; + curFile.writef("%d-%02d-%02d %02d:%02d:%02d.%03d ", time.year, time.month, time.day, time.hour, time.minute, time.second, msecs); + dst.put('['); + dst.put(level); + dst.put("] "); + } + + override void put(scope const(char)[] text) { + curFile.write(text); + } + + override void endLine() { + curFile.write("\x1b[0m"); + curFile.writeln(); + curFile.flush(); + } +} + +void deregisterAllLoggers() @trusted nothrow { + auto loggers = getLoggers(); + foreach (logger; loggers) { + deregisterLogger(logger); + } +}