struct Var { String key; String value; }; struct ConfigParseResult { Array vars; Array errors; }; struct ReplaceVar { String replace; String with; }; // @todo: move the config errors to config menu!!! char SRTCommand[512] = "\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" --start-time {time_in_seconds} {video}"; char PDFCommand[512] = "C:/Users/Karol/AppData/Local/SumatraPDF/SumatraPDF.exe -page {page} {file}"; char TXTCommand[512] = "notepad.exe {file}"; char ReadOnStart[512] = "-"; bool ReadFoldersRecursively = false; void SkipWhitespace(String *s) { while (s->len && IsWhitespace(s->data[0])) { *s = s->skip(1); } } String ParseWord(String *s) { String result = {}; SkipWhitespace(s); if (s->len && IsAlphabetic(s->data[0])) { result = {s->data, 0}; while (s->len && IsAlphanumeric(s->data[0])) { result.len += 1; *s = s->skip(1); } } return result; } bool MatchString(String *s, String expect) { SkipWhitespace(s); if (StartsWith(*s, expect)) { *s = s->skip(expect.len); return true; } return false; } ConfigParseResult ParseConfig(Allocator allocator, String string) { Scratch scratch; Array lines = Split(scratch, string, "\n"); ConfigParseResult result = {}; result.errors = {allocator}; result.vars = {allocator}; For(lines) { String s = Trim(it); if (s.len == 0) continue; String key = ParseWord(&s); if (key.len == 0) { result.errors.add(Format(allocator, "failed to parse config at line: %d, the key is invalid", (int)lines.get_index(it))); continue; } bool e = MatchString(&s, "="); if (!e) { result.errors.add(Format(allocator, "failed to parse config at line: %d, expected '=' assignment sign", (int)lines.get_index(it))); continue; } String value = Trim(s); if (value.len == 0) { result.errors.add(Format(allocator, "failed to parse config at line: %d, the value is invalid", (int)lines.get_index(it))); continue; } result.vars.add({key, value}); } return result; } String GetValue(Array vars, String key) { For(vars) { if (it.key == key) return it.value; } return {}; } String SerializeConfig(Allocator allocator) { String content = Format(allocator, "SRTCommand = %s\nPDFCommand = %s\nTXTCommand = %s\nReadOnStart = %s", SRTCommand, PDFCommand, TXTCommand, ReadOnStart); return content; } String CreateDefaultConfig(String path) { String content = SerializeConfig(Perm); WriteFile(path, content); return content; } void LoadConfig() { Scratch scratch; String exe_dir = GetExeDir(scratch); String config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir)); String config = ReadFile(Perm, config_path); if (config.len == 0) { config = CreateDefaultConfig(config_path); } ConfigParseResult result = ParseConfig(Perm, config); For(result.errors) { XFileLoadResults.add({"", it}); } String pdf_command = GetValue(result.vars, "PDFCommand"); if (pdf_command.len) { int len = ClampTop((int)pdf_command.len, (int)sizeof(PDFCommand) - 1); memcpy(PDFCommand, pdf_command.data, len); PDFCommand[len] = 0; } String txt_command = GetValue(result.vars, "TXTCommand"); if (txt_command.len) { int len = ClampTop((int)txt_command.len, (int)sizeof(TXTCommand) - 1); memcpy(TXTCommand, txt_command.data, len); TXTCommand[len] = 0; } String srt_command = GetValue(result.vars, "SRTCommand"); if (srt_command.len) { int len = ClampTop((int)srt_command.len, (int)sizeof(SRTCommand) - 1); memcpy(SRTCommand, srt_command.data, len); SRTCommand[len] = 0; } String read_on_start = GetValue(result.vars, "ReadOnStart"); if (read_on_start.len) { int len = ClampTop((int)read_on_start.len, (int)sizeof(ReadOnStart) - 1); memcpy(ReadOnStart, read_on_start.data, len); ReadOnStart[len] = 0; } } void SaveConfig() { Scratch scratch; String exe_dir = GetExeDir(scratch); String config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir)); String content = SerializeConfig(scratch); WriteFile(config_path, content); } String ReplaceVars(Allocator allocator, Array vars_to_replace, String string) { Array sb = {allocator}; for (int64_t i = 0; i < string.len; i += 1) { if (string[i] == '{') { // extract the variable i += 1; String var = {string.data + i, 0}; for (; i < string.len && string[i] != '}'; i += 1) var.len += 1; // find the replacement String found = ""; For(vars_to_replace) { if (it.replace == var) { found = it.with; break; } } For(found) sb.add(it); } else sb.add(string[i]); } sb.add('\0'); String result = {sb.data, sb.len - 1}; return result; } void TestReplaceVars() { Scratch scratch; Array vars_to_replace = {scratch}; String exe_folder = GetExePath(scratch); vars_to_replace.add({"exe_folder", exe_folder}); String data_folder = Format(scratch, "%.*s/data", FmtString(exe_folder)); vars_to_replace.add({"data_folder", data_folder}); { String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}"); Assert(r == exe_folder); } { String r = ReplaceVars(scratch, vars_to_replace, "{exe_folder}{data_folder}"); Assert(r == Format(scratch, "%.*s%.*s", FmtString(exe_folder), FmtString(data_folder))); } { String r = ReplaceVars(scratch, vars_to_replace, "..{exe_folder}..{data_folder}..{exe_folder}asd"); Assert(r == Format(scratch, "..%.*s..%.*s..%.*sasd", FmtString(exe_folder), FmtString(data_folder), FmtString(exe_folder))); } } void TestConfig() { Scratch scratch; { ConfigParseResult result = ParseConfig(scratch, "\nPDFCommand = CoolCommand\nTXTCommand = ABC\nSRTCommand = DAS\n"); Assert(result.errors.len == 0); Assert(result.vars.len == 3); Assert(result.vars[0].key == "PDFCommand"); Assert(result.vars[1].value == "ABC"); Assert(result.vars[2].value == "DAS"); Assert(result.vars[2].key == "SRTCommand"); } { ConfigParseResult result = ParseConfig(scratch, "\n= CoolCommand\nTXTCommand = ABC\nSRTCommand =\n"); Assert(result.errors.len == 2); Assert(result.vars.len == 1); } LoadConfig(); }