diff --git a/.generate_database/.gitignore b/.generate_database/.gitignore index 87b5aa52..46131a86 100644 --- a/.generate_database/.gitignore +++ b/.generate_database/.gitignore @@ -1,4 +1,5 @@ __pycache__ _data *.lua-table -*.log \ No newline at end of file +*.log +Translations \ No newline at end of file diff --git a/.generate_database_lua/Dockerfile b/.generate_database_lua/Dockerfile new file mode 100644 index 00000000..a20ef736 --- /dev/null +++ b/.generate_database_lua/Dockerfile @@ -0,0 +1,13 @@ +FROM nickblah/lua:5.1-luarocks + +RUN apt-get update && apt-get install -y git gcc +# Install OpenSSL for LuaSec +RUN apt-get install -y libssl-dev + +RUN luarocks install bit32 +RUN luarocks install argparse +RUN luarocks install luafilesystem +RUN luarocks install luasocket +RUN luarocks install luasec + +RUN apt-get update && apt-get install -y wget \ No newline at end of file diff --git a/.generate_database_lua/createStatic.lua b/.generate_database_lua/createStatic.lua new file mode 100644 index 00000000..fc905bc4 --- /dev/null +++ b/.generate_database_lua/createStatic.lua @@ -0,0 +1,170 @@ +-- Allow accessing private fields +---@diagnostic disable: invisible +require("cli.dump") +local argparse = require("argparse") +local helpers = require(".helpers") + +require("cli.Addon_Meta") +require("cli.CLI_Helpers") + +assert(Is_CLI, "This function should only be called from the CLI environment") + +local f = string.format + +Is_Create_Static = true + +function DumpDatabase(version) + local lowerVersion = version:lower() + local capitalizedVersion = lowerVersion:gsub("^%l", string.upper) + print(f("\n\27[36mCompiling %s database...\27[0m", capitalizedVersion)) + + -- Reset data objects, load the files and set wow version + LibQuestieDBTable = AddonInitializeVersion(capitalizedVersion) + + -- Drain all the timers + C_Timer.drainTimerList() + + local itemOverride = {} + local npcOverride = {} + local objectOverride = {} + local questOverride = {} + + local Corrections = LibQuestieDBTable.Corrections + + Corrections.DumpFunctions.testDumpFunctions() + + do + CLI_Helpers.loadFile(f(".generate_database/_data/%sItemDB.lua", lowerVersion)) + itemOverride = loadstring(QuestieDB.itemData)() + LibQuestieDBTable.Item.LoadOverrideData(false, true) + local itemMeta = Corrections.ItemMeta + for itemId, corrections in pairs(LibQuestieDBTable.Item.override) do + if not itemOverride[itemId] then + itemOverride[itemId] = {} + end + for key, correction in pairs(corrections) do + local correctionIndex = itemMeta.itemKeys[key] + itemOverride[itemId][correctionIndex] = correction + end + end + end + + do + CLI_Helpers.loadFile(f(".generate_database/_data/%sNpcDB.lua", lowerVersion)) + npcOverride = loadstring(QuestieDB.npcData)() + LibQuestieDBTable.Npc.LoadOverrideData(false, true) + local npcMeta = Corrections.NpcMeta + for npcId, corrections in pairs(LibQuestieDBTable.Npc.override) do + if not npcOverride[npcId] then + npcOverride[npcId] = {} + end + for key, correction in pairs(corrections) do + local correctionIndex = npcMeta.npcKeys[key] + npcOverride[npcId][correctionIndex] = correction + end + end + end + + do + CLI_Helpers.loadFile(f(".generate_database/_data/%sObjectDB.lua", lowerVersion)) + objectOverride = loadstring(QuestieDB.objectData)() + LibQuestieDBTable.Object.LoadOverrideData(false, true) + local objectMeta = Corrections.ObjectMeta + for objectId, corrections in pairs(LibQuestieDBTable.Object.override) do + if not objectOverride[objectId] then + objectOverride[objectId] = {} + end + for key, correction in pairs(corrections) do + local correctionIndex = objectMeta.objectKeys[key] + objectOverride[objectId][correctionIndex] = correction + end + end + end + + do + CLI_Helpers.loadFile(f(".generate_database/_data/%sQuestDB.lua", lowerVersion)) + questOverride = loadstring(QuestieDB.questData)() + LibQuestieDBTable.Quest.LoadOverrideData(false, true) + local questMeta = Corrections.QuestMeta + for questId, corrections in pairs(LibQuestieDBTable.Quest.override) do + if not questOverride[questId] then + questOverride[questId] = {} + end + for key, correction in pairs(corrections) do + local correctionIndex = questMeta.questKeys[key] + questOverride[questId][correctionIndex] = correction + end + end + end + + -- Write all the overrides to disk + ---@diagnostic disable-next-line: param-type-mismatch + -- local file = io.open(f(".generate_database/_data/output/Item/%s/ItemData.lua-table", capitalizedVersion), "w") + print("Dumping item overrides") + -- assert(file, "Failed to open file for writing") + local itemData = helpers.dumpData(itemOverride, Corrections.ItemMeta.itemKeys, Corrections.ItemMeta.dumpFuncs, Corrections.ItemMeta.combine) + ---@diagnostic disable-next-line: undefined-field + -- file:write(itemData) + ---@diagnostic disable-next-line: undefined-field + -- file:close() + + -- ---@diagnostic disable-next-line: param-type-mismatch + -- file = io.open(f(".generate_database/_data/output/Quest/%s/QuestData.lua-table", capitalizedVersion), "w") + -- print("Dumping quest overrides") + -- assert(file, "Failed to open file for writing") + -- local questData = helpers.dumpData(questOverride, Corrections.QuestMeta.questKeys, Corrections.QuestMeta.dumpFuncs) + -- ---@diagnostic disable-next-line: undefined-field + -- file:write(questData) + -- ---@diagnostic disable-next-line: undefined-field + -- file:close() + + -- ---@diagnostic disable-next-line: param-type-mismatch + -- file = io.open(f(".generate_database/_data/output/Npc/%s/NpcData.lua-table", capitalizedVersion), "w") + -- print("Dumping npc overrides") + -- assert(file, "Failed to open file for writing") + -- local npcData = helpers.dumpData(npcOverride, Corrections.NpcMeta.npcKeys, Corrections.NpcMeta.dumpFuncs, Corrections.NpcMeta.combine) + -- ---@diagnostic disable-next-line: undefined-field + -- file:write(npcData) + -- ---@diagnostic disable-next-line: undefined-field + -- file:close() + + -- ---@diagnostic disable-next-line: param-type-mismatch + -- file = io.open(f(".generate_database/_data/output/Object/%s/ObjectData.lua-table", capitalizedVersion), "w") + -- print("Dumping object overrides") + -- assert(file, "Failed to open file for writing") + -- local objectData = helpers.dumpData(objectOverride, Corrections.ObjectMeta.objectKeys, Corrections.ObjectMeta.dumpFuncs) + -- ---@diagnostic disable-next-line: undefined-field + -- file:write(objectData) + -- ---@diagnostic disable-next-line: undefined-field + -- file:close() + + print(f("\n\27[32m%s corrections dumped successfully\27[0m", capitalizedVersion)) +end + +-- local validVersions = { +-- ["era"] = true, +-- ["tbc"] = true, +-- ["wotlk"] = true, +-- } +-- local versionString = "" +-- for version in pairs(validVersions) do +-- local v = string.gsub(version, "^%l", string.upper) +-- versionString = versionString .. v .. "/" +-- end +-- -- Add all +-- versionString = versionString .. "All" + +-- local parser = argparse("createStatic", "createStatic.lua Era") +-- parser:argument("version", f("Game version, %s.", versionString)) + +-- local args = parser:parse() + +-- if args.version and validVersions[args.version:lower()] then +-- DumpDatabase(args.version) +-- elseif args.version and args.version:lower() == "all" then +-- for version in pairs(validVersions) do +-- DumpDatabase(version) +-- end +-- else +-- print("No version specified") +-- end diff --git a/.generate_database_lua/docker-compose.yml b/.generate_database_lua/docker-compose.yml new file mode 100644 index 00000000..e3bb49d5 --- /dev/null +++ b/.generate_database_lua/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' + +services: + lua_2: + #image: nickblah/lua:5.1-luarocks + build: . + command: sh /QuestieDB/.generate_database_lua/run.sh + volumes: + - '../:/QuestieDB' diff --git a/.generate_database_lua/generate_translation_trie.lua b/.generate_database_lua/generate_translation_trie.lua new file mode 100644 index 00000000..a852747b --- /dev/null +++ b/.generate_database_lua/generate_translation_trie.lua @@ -0,0 +1,183 @@ +require("cli.dump") + +-- Define the maximum number of translations per file +local MAX_TRANSLATIONS_PER_FILE = 35 +local SEGMENT_SIZE = 4000 +local REDUCE_SEGMENT_SIZE = math.max(math.min(SEGMENT_SIZE * 0.05, 100), 10) +print("Max translations per file: " .. MAX_TRANSLATIONS_PER_FILE) +print("Segment size: " .. SEGMENT_SIZE, "Reduced segment size: " .. SEGMENT_SIZE - REDUCE_SEGMENT_SIZE) + +-- Function to sanitize translation strings by replacing special characters with HTML entities +local function sanitize_translation(str) + str = string.gsub(str, "&", "&") + str = string.gsub(str, "<", "<") + str = string.gsub(str, ">", ">") + return str +end + +-- Function to create directories recursively using os.execute +local function mkdir(path) + os.execute("mkdir -p " .. path) +end + +-- Function to split a string into segments based on maximum characters per segment +local function split_into_segments(str, max_chars) + local segments = {} + local total_segments = math.ceil(#str / max_chars) + + -- Loop through the string and create segments + for i = 1, total_segments do + local start_pos = (i - 1) * max_chars + 1 + local end_pos = math.min(i * max_chars, #str) + local segment = string.sub(str, start_pos, end_pos) + -- Add segment number prefix for all segments except the first one + if i > 1 then + segment = tostring(i) .. segment + end + table.insert(segments, segment) + end + + -- Add total segments count to the first segment + segments[1] = total_segments .. segments[1] + + return segments +end + +-- Function to write translations to an HTML file with segmentation +local function write_html_file(key_path, translations) + -- Define the file path + local filename = key_path .. ".html" + + -- Open the file for writing + local file, err = io.open(filename, "w") + if not file then + print("Error opening file " .. filename .. ": " .. err) + return + end + + -- Write the HTML structure + file:write("\n") + for _, translation in ipairs(translations) do + -- Check if the translation needs to be segmented + if #translation > SEGMENT_SIZE - REDUCE_SEGMENT_SIZE then + print("Splitting translation into segments", translation) + -- Split the translation into segments + local segments = split_into_segments(translation, SEGMENT_SIZE - REDUCE_SEGMENT_SIZE) + -- Write each segment as a separate paragraph + for _, segment in ipairs(segments) do + file:write("

" .. sanitize_translation(segment) .. "

\n") + end + else + -- Write the translation as a single paragraph + file:write("

" .. sanitize_translation(translation) .. "

\n") + end + end + file:write("\n") + file:close() +end + +-- Function to create a branch in the trie structure +local function create_branch(strings, stringIndex) + local branch = {} + -- Process each string in the input array + for i = 1, #strings do + local string = strings[i] + local cleanedString = string.gsub(string, "%s", "") + -- Remove all numbers from the string + -- cleanedString = string.gsub(cleanedString, "%d+", "") + cleanedString = string.gsub(cleanedString, "%p", "") + cleanedString = string.gsub(cleanedString, "%c", "") + + -- Get the character at the current index + -- local char = string.sub(string.lower(cleanedString), stringIndex, stringIndex) + local char = string.sub(cleanedString, stringIndex, stringIndex) + + if char == "" then + error(string.format("%s: %d out of range, increase MAX_TRANSLATIONS_PER_FILE", string, stringIndex)) + end + + -- Create a new branch for the character if it doesn't exist + if not branch[char] then + branch[char] = {} + end + table.insert(branch[char], string) + end + + -- Recursively create branches for child nodes if needed + for char, child in pairs(branch) do + if #child > MAX_TRANSLATIONS_PER_FILE then + branch[char] = create_branch(child, stringIndex + 1) + end + end + + return branch +end + +-- Function to create trie folders and write translations to HTML files +local function create_trie_folders(trie, current_path) + print("Current path: " .. current_path) + for key, value in pairs(trie) do + local new_path = current_path .. "/" .. key + if type(value) == "table" then + -- If the number of translations is greater than the maximum or there are no translations + if #value > MAX_TRANSLATIONS_PER_FILE or #value == 0 then + mkdir(new_path) + -- print("Continuing recursion for: " .. new_path) + create_trie_folders(value, new_path) + else + print("Writing HTML file for: " .. new_path) + write_html_file(new_path, value) + end + end + end +end + +-- Main function to compile translations to HTML +function Compile_translations_to_html(strings) + -- Initialize the trie + local success, trie + repeat + success, trie = pcall(create_branch, strings, 1) + if not success then + MAX_TRANSLATIONS_PER_FILE = MAX_TRANSLATIONS_PER_FILE + 1 + print(trie) + print("Shortest word does not fit! - increasing MAX_TRANSLATIONS_PER_FILE to: " .. MAX_TRANSLATIONS_PER_FILE) + end + until success + + -- Create the root folder + local root_folder = "translations" + mkdir(root_folder) + + -- Create trie folders and write translations + create_trie_folders(trie, root_folder) + + -- DevTools_Dump(trie) + -- Dump the trie to a lua file + -- local lines = {} + local function dump_trie(trie, indent) + local lines = {} + local indent_str = string.rep(" ", indent) + if type(trie) == "table" then + for char, value in pairs(trie) do + if type(value) == "table" then + table.insert(lines, indent_str .. "['" .. char .. "'] = {") + table.insert(lines, dump_trie(value, indent + 1)) + table.insert(lines, indent_str .. "},") + else + table.insert(lines, indent_str .. "\"" .. value .. "\",") + end + end + else + table.insert(lines, indent_str .. "[\"" .. trie .. "\"]") + end + return table.concat(lines, "\n") + end + + local lua_file = io.open(root_folder .. "/trie.lua", "w") + if lua_file ~= nil then + local dump_str = dump_trie(trie, 1) + lua_file:write("local trie = {\n" .. dump_str .. "\n}") + lua_file:close() + end +end diff --git a/.generate_database_lua/generate_translation_trie_root.lua b/.generate_database_lua/generate_translation_trie_root.lua new file mode 100644 index 00000000..93d78ae3 --- /dev/null +++ b/.generate_database_lua/generate_translation_trie_root.lua @@ -0,0 +1,359 @@ +require("cli.dump") + +---@alias trie table +---@alias filepath string + +-- The root folder +local root_folder = "Translations" +local data_folder = "_data" +local full_path = root_folder .. "/" .. data_folder + +-- Variable to disable the writing of html +local write_html = true + +-- Double Dagger +local splitCharacter = "‡" + +-- Define the maximum number of translations per file +local MAX_TRANSLATIONS_PER_FILE = 50 +local SEGMENT_SIZE = 4000 +local REDUCE_SEGMENT_SIZE = math.max(math.min(SEGMENT_SIZE * 0.05, 100), 10) + +local f = string.format + +-- From here +-- https://github.com/Questie/Questie/blob/2e2c44dc42dd66fb144be8ba0115287da5b7cd8e/Localization/l10n.lua#L22 +local localeOrder = { + 'enUS', + 'esES', + 'esMX', + 'ptBR', + 'frFR', + 'deDE', + 'ruRU', + 'zhCN', + 'zhTW', + 'koKR', +} + +print("Max translations per file: " .. MAX_TRANSLATIONS_PER_FILE) +print("Segment size: " .. SEGMENT_SIZE, "Reduced segment size: " .. SEGMENT_SIZE - REDUCE_SEGMENT_SIZE) + +local tConcat = table.concat +local tInsert = table.insert + +--- Function to sanitize translation strings by replacing special characters with HTML entities +---@param str string +---@return string +local function sanitize_translation(str) + str = string.gsub(str, "&", "&") + str = string.gsub(str, "<", "<") + str = string.gsub(str, ">", ">") + -- str = string.gsub(str, "\n", "
") + -- str = string.gsub(str, '"', '\\"') + return str +end + +--- Function to create directories recursively using os.execute +---@param path filepath +local function mkdir(path) + os.execute("mkdir -p " .. path) +end + +--- Function to split a table of strings into segments based on maximum characters per segment +---@param tbl string[] The table of strings to split +---@param max_chars number The maximum number of characters per segment +---@return string[] A table of segments, each being a table of strings +local function split_into_segments(tbl, max_chars) + local segments = {} + local current_segment = {} + + -- Is the total length too long? + if #tConcat(tbl, splitCharacter) > max_chars then + local concat_current_segments = tConcat(current_segment, splitCharacter) + for _, segment in ipairs(tbl) do + -- Will the current length + the next one be over max_chars? + if #concat_current_segments + #segment > max_chars then + -- Insert a segment and start a new one + tInsert(segments, concat_current_segments) + current_segment = { segment, } + else + -- We are below the max_chars limit, so we can add the next string to the current segment + tInsert(current_segment, segment) + end + end + -- Insert the last segment + tInsert(segments, concat_current_segments) + end + + -- Iterate over each segment and write it as a separate paragraph + local ret_segments = {} + for idx, segment in ipairs(segments) do + -- If it's the first segment, prepend the total segments count + if idx == 1 and #segments > 0 then + segment = tostring(#segments) .. segment + elseif idx > 1 then + segment = tostring(idx) .. segment + end + tInsert(ret_segments, segment) + end + + return ret_segments +end + +--- Function to write translations to an HTML file with segmentation +---@param key_path string The path for the translation e.g. translations/u/s/e/t/h/e +---@param translations string[] +---@param translation_func fun(string):string[], string A function that takes the english string and returns all translations +local function write_html_file(key_path, translations, translation_func) + -- ? This stops the function from writing in subfolders but instead writes in the root folder + -- ? Remember to activate the folder creation function in the create_trie_folders function if disabled + -- "%-" Always escape + local replaceChar = "" + key_path = key_path:gsub("/", replaceChar) + key_path = key_path:gsub(full_path:gsub("/", replaceChar) .. replaceChar, full_path .. "/", 1) -- We also removed the root folder from the key_path, add back the / + + -- Define the file path + local filename = key_path .. ".html" + + -- Open the file for writing + if write_html then + local file, err = io.open(filename, "w") + if not file then + print("Error opening file " .. filename .. ": " .. err) + return + end + + -- Write the HTML structure + file:write("\n") + for _, translation in ipairs(translations) do + local fullTranslationTable, enUS = translation_func(translation) + file:write("\n") + + if #tConcat(fullTranslationTable, splitCharacter) > SEGMENT_SIZE - REDUCE_SEGMENT_SIZE then + print("Splitting translation into segments", translation, filename) + -- Split the translation into segments + local segments = split_into_segments(fullTranslationTable, SEGMENT_SIZE - REDUCE_SEGMENT_SIZE) + for _, segment in ipairs(segments) do + file:write("

" .. sanitize_translation(segment) .. "

\n") + end + else + -- Write the translation as a single paragraph + local concatenated = tConcat(fullTranslationTable, splitCharacter) + file:write("

" .. sanitize_translation(concatenated) .. "

\n") + end + end + file:write("\n") + file:close() + end +end + +-- Function to create trie folders and write translations to HTML files +---comment +---@param trie trie +---@param current_path filepath +---@param translation_func fun(translation:string):string[], string A function that takes the english string and returns all translations +local function write_trie_structure(trie, current_path, translation_func) + -- print("Current path: " .. current_path) + for trieKey, translations in pairs(trie) do + local new_path = current_path .. "/" .. trieKey + if type(translations) == "table" then + -- If the number of translations is greater than the maximum or there are no translations + if #translations > MAX_TRANSLATIONS_PER_FILE or #translations == 0 then + -- ? This line activates the function to create folders for the current path, disabled due to easier to just have it flat. + -- * mkdir(new_path) + + -- print("Continuing recursion for: " .. new_path) + write_trie_structure(translations, new_path, translation_func) + else + -- print("Writing HTML file for: " .. new_path) + write_html_file(new_path, translations, translation_func) + end + end + end +end + +--- Function to create a branch in the trie structure +---@param strings string[] +---@param stringIndex number @ The index of the current character in the string +---@return trie +local function create_trie(strings, stringIndex) + local branch = {} + -- Process each string in the input array + for i = 1, #strings do + local string = strings[i] + -- Remove all whitespaces from the string + local cleanedString = string.gsub(string, "%s", "") + -- Remove all numbers from the string (Not in use as some strings are very short and contains numbers) + -- cleanedString = string.gsub(cleanedString, "%d+", "") + -- Remove all punctuation from the string + cleanedString = string.gsub(cleanedString, "%p", "") + -- Remove all control characters from the string + cleanedString = string.gsub(cleanedString, "%c", "") + + -- Get the character at the current index + -- local char = string.sub(string.lower(cleanedString), stringIndex, stringIndex) + local char = string.sub(cleanedString, stringIndex, stringIndex) + + if char == "" then + -- error(f("%s: %d out of range, increase MAX_TRANSLATIONS_PER_FILE", string, stringIndex)) + print(f("%s: %d out of range", string, stringIndex)) + char = "." + -- tInsert(parent[char], string) + -- else + end + -- Create a new branch for the character if it doesn't exist + if not branch[char] then + branch[char] = {} + end + tInsert(branch[char], string) + end + + -- Recursively create branches for child nodes if needed + for char, child in pairs(branch) do + if #child > MAX_TRANSLATIONS_PER_FILE then + branch[char] = create_trie(child, stringIndex + 1) + end + end + + return branch +end + +-- Function to check if a table is an array of strings +local function isStringArray(t) + if type(t) ~= "table" then + return false + end + -- Check if the table is a sequence (array) with consecutive integer keys starting at 1 + local index = 1 + for k, v in pairs(t) do + if type(k) ~= "number" or k ~= index then + return false + end + if type(v) ~= "string" then + return false + end + index = index + 1 + end + return true +end + +-- Recursive function to traverse and modify the trie table +local function replaceArrays(t, filepath) + for key, value in pairs(t) do + if type(value) == "table" then + if isStringArray(value) then + -- Replace the array of strings with the filepath + t[key] = filepath .. key .. ".html" + else + -- Recursively process nested tables + replaceArrays(value, filepath .. key) + end + end + end +end + +-- Main function to compile translations to HTML +---@param strings string[] +---@param addonName string The folder name for the addon, it is for the path in the XML files +---@param translation_func fun(string):table A function that takes the english string and returns all translations +function Compile_translations_to_html(strings, addonName, translation_func) + -- Initialize the trie + local success, trie + repeat + success, trie = pcall(create_trie, strings, 1) + if not success then + MAX_TRANSLATIONS_PER_FILE = MAX_TRANSLATIONS_PER_FILE + 1 + print(trie) + print("Shortest word does not fit! - increasing MAX_TRANSLATIONS_PER_FILE to: " .. MAX_TRANSLATIONS_PER_FILE) + end + until success + + mkdir(root_folder) + mkdir(root_folder .. "/" .. data_folder) + + ---@param enUStext string + ---@return string[]? + ---@return string enUS translation + -- -@return error? + local function getTranslation(enUStext) + local allTranslations, err = translation_func(enUStext) + if err then + return nil, err + end + + local enUSTranslation = enUStext -- "enUS" .. "[" .. enUStext .. "]" + local combinedTranslations = {} + for _, locale in ipairs(localeOrder) do + local text = allTranslations[locale] + if type(text) == "string" and locale ~= "enUS" then + tInsert(combinedTranslations, locale .. "[" .. text .. "]") + elseif locale ~= "enUS" then + tInsert(combinedTranslations, "") + end + end + return combinedTranslations, enUSTranslation --tConcat(combinedTranslations, "‡") + end + + -- Create trie folders and write translations + write_trie_structure(trie, full_path, getTranslation) + + -- Replace the actual string arrays with the template xml name + replaceArrays(trie, "") + + local allHTMLFiles = {} + -- Function to print the table for verification (optional) + -- Function to print the table for verification and collect HTML files + ---@param t table The table to print + ---@param indent string? The indentation string + ---@return string The formatted table string + local function printTable(t, indent) + -- Reset the HTML files if we are on the first step. + if indent == "" then + allHTMLFiles = {} + end + local lines = {} + indent = indent or "" + for k, v in pairs(t) do + if type(v) == "table" then + tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = {") + tInsert(lines, printTable(v, indent .. " ")) + tInsert(lines, indent .. "},") + else + -- if html in v use " otherwise it will be wrapped in [[]] + if string.find(v, ".html") then + tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = \"" .. tostring(v) .. "\",") + tInsert(allHTMLFiles, v) + else + tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = [[" .. tostring(v) .. "]],") + end + end + end + return tConcat(lines, "\n") + end + + -- ? Generate the lookup file loaded in Lua + local lua_file = io.open(root_folder .. "/TranslationsLookup_gen.lua", "w") + if lua_file then + local dump_str = printTable(trie, "") --dump_trie(point_to_html(trie), 1) + lua_file:write("-- ! File generated by generate_translation_trie_root.lua --\n") + lua_file:write("-- ! DO NOT EDIT --\n") + lua_file:write("---@class LibQuestieDB\n") + lua_file:write("---@field translationsLookup table|string> Contains lookup for HTML files for translations\n") + lua_file:write("local LibQuestieDB = select(2, ...)\n") + lua_file:write("\n") + lua_file:write(f("LibQuestieDB.translationsLookup = {\n%s\n}", dump_str)) + lua_file:close() + end + + -- ? Generate the XML file that creates the virtual SimpleHTML objects + local fileString = '\n' + lua_file = io.open(root_folder .. "/TranslationsFiles_gen.xml", "w") + if lua_file then + lua_file:write('\n') + for _, htmlfile in pairs(allHTMLFiles) do + lua_file:write(f(fileString, htmlfile, addonName, data_folder, htmlfile)) + end + lua_file:write('') + end +end diff --git a/.generate_database_lua/helpers.lua b/.generate_database_lua/helpers.lua new file mode 100644 index 00000000..e2859716 --- /dev/null +++ b/.generate_database_lua/helpers.lua @@ -0,0 +1,222 @@ +-- helpers.lua +local lfs = require("lfs") + +-- Require the necessary LuaSocket modules +local http = require("socket.http") +local https = require("ssl.https") +local ltn12 = require("ltn12") + +--- Get the script directory. +---@return string The directory containing the script. +local function get_script_dir() + local script_path = debug.getinfo(1, "S").source:sub(2) + return script_path:match("(.*/)") +end + +--- Get the project directory path. +---@return string The project directory path. +local function get_project_dir_path() + return get_script_dir() .. ".." +end + +--- Get the data directory path for a given entity type and expansion. +---@param entity_type string The type of entity (e.g., "Quest", "Item"). +---@param expansion string The expansion name (e.g., "Era", "Tbc", "Wotlk"). +---@return string The data directory path. +local function get_data_dir_path(entity_type, expansion) + local path = get_project_dir_path() .. "/Database/" .. entity_type .. "/" .. expansion + -- Does path not exist, l10n has this issue... + if not lfs.attributes(path, "mode") then + print("Path " .. path .. " does not exist, trying lowercase") + path = get_project_dir_path() .. "/Database/" .. entity_type:lower() .. "/" .. expansion + end + return path +end + +--- Find the addon name. +---@return string The addon name. +local function find_addon_name() + local current_dir = lfs.currentdir() + local previous_dir = nil + local addon_dir = nil + local max_level = 20 + local level = 0 + + while level < max_level do + local dir_name = current_dir:match("([^/]+)$") + local parent_dir = current_dir:match("^(.*)/[^/]+$") + if type(dir_name) == "nil" then + addon_dir = previous_dir + break + elseif dir_name:lower() == "addons" and parent_dir:match("([^/]+)$"):lower() == "interface" then + addon_dir = previous_dir + break + else + previous_dir = current_dir + current_dir = parent_dir + level = level + 1 + end + end + + if not addon_dir then + print("Could not find the Addons folder, defaulting to 'QuestieDB'.") + return "QuestieDB" + else + print("Found Addons folder: " .. addon_dir) + end + + -- Remove / or \ characters + addon_dir = addon_dir:gsub("[/\\]", "") + + return addon_dir +end + +--- Read expansion data from a Lua file. +---@param expansion string The expansion name (e.g., "Era", "Tbc", "Wotlk"). +---@param entity_type string The type of entity (e.g., "Quest", "Item"). +---@return string|nil The content of the Lua file, or nil if not found. +local function read_expansion_data(expansion, entity_type) + local path = get_data_dir_path(entity_type, expansion) + print("Reading " .. expansion .. " lua " .. entity_type:lower() .. " data from " .. path) + local file_path = path .. "/" .. entity_type:sub(1, 1):upper() .. entity_type:sub(2):lower() .. "Data.lua-table" + + if not lfs.attributes(file_path, "mode") then + file_path = path .. "/" .. entity_type:lower() .. "Data.lua-table" + if not lfs.attributes(file_path, "mode") then + print("File not found: " .. file_path) + return nil + end + end + + local file, err = io.open(file_path, "r") + if not file then + print("File not found: " .. path) + return nil + end + + local data = file:read("*all") + file:close() + + -- Perform any necessary replacements on the data string + data = data:gsub("&", "and"):gsub("<", "|"):gsub(">", "|") + return data +end + +--- Download a raw text file from a URL and return its contents as a string. +-- @param url string The URL of the text file to download. +-- @return string|nil The contents of the text file, or nil if the download fails. +local function download_text_file(url) + local response_body = {} + local res, code, response_headers = https.request { + url = url, + sink = ltn12.sink.table(response_body), + } + + if res == 1 and code == 200 then + -- Concatenate the table into a single string + return table.concat(response_body) + else + print("Failed to download file: HTTP response code " .. tostring(code)) + return nil + end +end + +---Dumps the data for Item, Quest, Npc, Object into a string +---@param tbl table> @ Table that will be dumped, Item, Quest, Npc, Object +---@param dataKeys ItemDBKeys|QuestDBKeys|NpcDBKeys|ObjectDBKeys @ Contains name of data as keys and their index as value +---@param dumpFunctions table @ Contains the functions that will be used to dump the data +---@param combineFunc function? @ Function that will be used to combine the data, if nil the data will not be combined +---@return string,table[] @ Returns the dumped data as a string and a table of the dumped data +local function dumpData(tbl, dataKeys, dumpFunctions, combineFunc) + -- sort tbl by key + local sortedKeys = {} + for key in pairs(tbl) do + sortedKeys[#sortedKeys + 1] = key + end + table.sort(sortedKeys) + + local nrDataKeys = 0 + local reversedKeys = {} + for key, id in pairs(dataKeys) do + reversedKeys[id] = key + nrDataKeys = nrDataKeys + 1 + end + + local allResultsTbl = {} + local allResults = { "{\n", } + for sortKeyIndex = 1, #sortedKeys do + local sortKey = sortedKeys[sortKeyIndex] + + -- print(sortKey) + local value = tbl[sortKey] + + local resulttable = {} + for i = 1, nrDataKeys do + resulttable[i] = "nil" + end + + for key = 1, nrDataKeys do + -- The name of the key e.g. "objectDrops" + local dataName = dataKeys[key] == nil and reversedKeys[key] or key + -- The id of the key e.g. "3"-(objectDrops) + local dataKey = type(key) == "number" and key or dataKeys[key] + + -- print(key, dataName, dataKey) + + -- Get the data from the table + local data = value[key] + + -- Because we build it with nil we have to check for nil here, if the value is nil we just print nil + if data ~= "nil" and data ~= nil then + local dumpFunction = dumpFunctions[dataName] + if dumpFunction then + local dumpedData = dumpFunction(data) + resulttable[dataKey] = dumpedData + else + error("No dump function for key: " .. "dataName" .. " (" .. tostring(dataName) .. ")" .. " dataKey: " .. tostring(dataKey)) + end + else + resulttable[dataKey] = "nil" + end + end + -- DevTools_Dump({resulttable}) + + -- If a combine funnction exist we run it here + assert(#resulttable == nrDataKeys, "resulttable length is not equal to dataKeys length, combine will fail") + if combineFunc then + combineFunc(resulttable) + end + -- DevTools_Dump({resulttable}) + + -- Concat the data into a string + local data = table.concat(resulttable, ",") + + allResultsTbl[sortKey] = data + + -- Remove trailing nil + repeat + local count = 0 + data, count = string.gsub(data, ",nil$", "") + until count == 0 + + -- Add the data to the result + allResults[#allResults + 1] = " [" .. sortKey .. "] = {" + allResults[#allResults + 1] = data + allResults[#allResults + 1] = ",},\n" + end + allResults[#allResults + 1] = "}" + -- return result .. "}" + return table.concat(allResults), allResultsTbl +end + +---@class helpers +local return_table = { + get_project_dir_path = get_project_dir_path, + get_data_dir_path = get_data_dir_path, + find_addon_name = find_addon_name, + read_expansion_data = read_expansion_data, + dumpData = dumpData, + download_text_file = download_text_file, +} +-- Expose functions +return return_table diff --git a/.generate_database_lua/main.lua b/.generate_database_lua/main.lua new file mode 100644 index 00000000..7e3d07d2 --- /dev/null +++ b/.generate_database_lua/main.lua @@ -0,0 +1,116 @@ +-- main.lua +-- Prepend your script's directory to the package.path +local script_path = debug.getinfo(1, "S").source:sub(2) +local script_dir = script_path:match("(.*/)") +package.path = script_dir .. "?.lua;" .. package.path + +local helpers = require("helpers") +require("cli.CLI_Helpers") + +-- Main function to demonstrate the usage of helper functions +local function main() + -- Get the project directory path + local project_dir = helpers.get_project_dir_path() + print("Project Directory Path: " .. project_dir) + + -- Get the data directory path for a specific entity type and expansion + local entity_type = "Quest" + local expansion = "Wotlk" + local data_dir = helpers.get_data_dir_path(entity_type, expansion) + print("Data Directory Path for " .. entity_type .. " in " .. expansion .. ": " .. data_dir) + + -- Find the addon name + local addon_name = helpers.find_addon_name() + print("Addon Name: " .. addon_name) + + -- Read expansion data + local expansion_data = helpers.read_expansion_data(expansion, entity_type) + if expansion_data then + local lines = 4 + local count = 0 + for line in expansion_data:gmatch(" [^\n]+") do + print(line) + count = count + 1 + if count >= lines then + break + end + end + else + print("No data found for " .. entity_type .. " in " .. expansion) + end + + -- Download a file from a URL + local url = "https://raw.githubusercontent.com/Questie/Questie/master/Database/Classic/classicItemDB.lua" + local data = helpers.download_text_file(url) + if data then + print("Downloaded data from URL: ") + local lines = 4 + local count = 0 + for line in data:gmatch(" [^\n]+") do + print(line) + count = count + 1 + if count >= lines then + break + end + end + else + print("Failed to download data from URL: " .. url) + end + + -- Write data to a file + -- Write data to a file + local output_dir = ".generate_database/_data" + local output_file = output_dir .. "/eraItemDB.lua" + + -- Write the data to the file + local file, err = io.open(output_file, "w") + if not file then + print("Error opening file for writing: " .. err) + else + file:write(data) + file:close() + print("Data written to file: " .. output_file) + end +end + + +require("cli.Addon_Meta") +local translations = {} +QuestieLoader = { + ImportModule = function() + return { translations = translations, } + end, +} + +CLI_Helpers.loadTOC(".generate_database_lua/translations.toc") + + +local single_translation = {} +for key, value in pairs(translations) do + -- local translation = string.gsub(key, "\n", "
") + -- translation = string.gsub(translation, '"', '\\"') + table.insert(single_translation, key) +end + + +---comment +---@param enUStext string +---@return table? +---@return error? +local function getTranslation(enUStext) + if translations[enUStext] then + return translations[enUStext], nil + else + return nil, "Translation not found for: " .. enUStext + end +end + + +require("generate_translation_trie_root") +-- Find the addon name +local addon_name = helpers.find_addon_name() +print("Addon Name: " .. addon_name) +Compile_translations_to_html(single_translation, addon_name, getTranslation) + +-- Run the main function +-- main() diff --git a/.generate_database_lua/run.sh b/.generate_database_lua/run.sh new file mode 100644 index 00000000..743969e3 --- /dev/null +++ b/.generate_database_lua/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +echo "Current directory: $(pwd)" +echo "$(which $1) is the lua executable" + +# First argument points to lua executable set "lua" if not set +if [ -z "$1" ]; then + LUA=lua +else + LUA=$1 +fi + +# Needed for the docker container but not action but it doesn't hurt the run if it fails +cd /QuestieDB + + + +$LUA ./.generate_database_lua/main.lua +# $LUA ./.generate_database_lua/generate_translation_trie.lua \ No newline at end of file diff --git a/.gitignore b/.gitignore index 991aacd7..a5868770 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ Database/*/**/*.html Database/*/*/*Data*.xml Database/*/*/*Data*.html +Translations/*_gen.xml +Translations/*_gen.lua +Translations/_data/*.html + .shit .translator @@ -54,4 +58,5 @@ Perfy .build -.llm-output \ No newline at end of file +.llm-output +..wiki-information \ No newline at end of file diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..b5695012 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + // "workspace.library": ["path/to/library/directory"], + "workspace.ignoreDir":[ + ".generate_database", + ".vscode", + ".translator", + ".wowhead", + "**/.git", + "**/.cache", + "**/_data/**", + ".llm-output", + ".build", + "Perfy", + ], + // "runtime.version": "Lua 5.3", + // "hint.enable": false +} + diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 00000000..e9636bd0 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 160 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "None" diff --git a/.vscode/launch.json b/.vscode/launch.json index eade0158..4e15717c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,9 @@ "console": "internalConsole", "justMyCode": true, "cwd": "${workspaceFolder}/.translator", - "args": [] + "args": [ + "Era" + ] } ] } \ No newline at end of file diff --git a/Corrections/Corrections.lua b/Corrections/Corrections.lua index f00b8c1a..ff5d5888 100644 --- a/Corrections/Corrections.lua +++ b/Corrections/Corrections.lua @@ -169,7 +169,9 @@ do dynamicCorrections[i] = correctionObject end end - + -- TODO: How do i remove this and keep the possiblity to load new corrections? + -- Corrections[capitalizedTypeStatic] = nil + -- Corrections[capitalizedTypeDynamic] = nil return { dynamic = dynamicCorrections, static = staticCorrections, diff --git a/Corrections/DumpFunctions.lua b/Corrections/DumpFunctions.lua index 2fe5d4d9..8c395a2b 100644 --- a/Corrections/DumpFunctions.lua +++ b/Corrections/DumpFunctions.lua @@ -29,6 +29,9 @@ end local tblMaxIndex = DumpFunctions.tblMaxIndex +---Counts the number of key-value pairs in the given table. +---@param tbl table The table to count the number of pairs in. +---@return number count The number of key-value pairs in the table. function DumpFunctions.tblCount(tbl) local count = 0 for _ in pairs(tbl) do diff --git a/Corrections/Sod/base/sodBaseItems.lua b/Corrections/Sod/base/sodBaseItems.lua index ebd891b3..f3218222 100644 --- a/Corrections/Sod/base/sodBaseItems.lua +++ b/Corrections/Sod/base/sodBaseItems.lua @@ -8,8 +8,8 @@ local ItemMeta = Corrections.ItemMeta ---@class ItemBaseSod local ItemBase = {} ---? This is the "static" database for SOD, it always have to load first of all SOD fixes. ---? Sod should however always load last. +--? This is the "static" database for SOD, out of ALL SoD fixes it should always load first. +--? SoD as "expansion" should ALWAYS load last. C_Timer.After(0, function() if LibQuestieDB.IsSoD then Corrections.RegisterCorrectionDynamic("item", diff --git a/Database/Base/Base.lua b/Database/Base/Base.lua index c829e432..d16576bc 100644 --- a/Database/Base/Base.lua +++ b/Database/Base/Base.lua @@ -53,8 +53,10 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType ---@type table> local override = {} - ---- Contains the id strings ---- - local AllIdStrings = {} + ---- Contains the id string ---- + ---- Use GetAllIds function to get the ids + ---@type string + local AllIdStrings = "" ---- Add entity type to the database ---- Database.entityTypes[captializedType] = true @@ -197,10 +199,15 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType -- If there are new IDs, concatenate them into a string and add to `AllIdStrings` for tracking. if #newIds ~= 0 then - tInsert(AllIdStrings, tConcat(newIds, ",")) + -- To get the right length we just print this message before adding the old string if Database.debugPrintEnabled then LibQuestieDB.ColorizePrint("lightBlue", f(" # New %s IDs", captializedType), #newIds) end + -- Add the old ID string to the new list of ids + -- e.g { "1", "2", "3", "4,5,6" } -> "1,2,3,4,5,6" + ---@diagnostic disable-next-line: assign-type-mismatch + newIds[#newIds + 1] = AllIdStrings + AllIdStrings = tConcat(newIds, ",") end -- Apply the override data to the database and return the number of overrides applied. @@ -212,10 +219,11 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType -- It will however still initialize during the CLI testing phase. if Is_Create_Static then return end - wipe(AllIdStrings) + -- Reset the `AllIdStrings` variable to an empty string. + AllIdStrings = "" local func, idString = Database.GetAllEntityIdsFunction(captializedType) -- TODO: Maybe we should sort this list? - tInsert(AllIdStrings, idString) + AllIdStrings = idString if Database.debugPrintEnabled then assert(#func() == #DB.GetAllIds(), f("%s ids are not the same", captializedType)) end @@ -241,12 +249,12 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType function DB.GetAllIds(hashmap) if hashmap == true then -- Substitute all numbers in the concatenated ID strings with Lua table format [number]=true - local dat = gsub(tConcat(AllIdStrings, ","), "(%d+)", "[%1]=true") + local dat = gsub(AllIdStrings, "(%d+)", "[%1]=true") -- Execute the string as Lua code to create and return the hashmap return loadstring(f("return {%s}", dat))() else -- If hashmap is not requested, simply return a list of IDs - return loadstring(f("return {%s}", tConcat(AllIdStrings, ",")))() + return loadstring(f("return {%s}", AllIdStrings))() end end diff --git a/Database/Database.lua b/Database/Database.lua index 8c280c7f..662aef97 100644 --- a/Database/Database.lua +++ b/Database/Database.lua @@ -46,21 +46,24 @@ local _nil = Database._nil ---- Local Functions ---- --* For performance reasons we check Is_CLI here, Database.CreateFrame supports both CLI and WOW -local CreateFrame = Is_CLI and Database.CreateFrame or CreateFrame -local frameType = "SimpleHTML" +local CreateFrame = Is_CLI and Database.CreateFrame or CreateFrame +local frameType = "SimpleHTML" local strsplittable = strsplittable -local tConcat = table.concat - -local tonumber = tonumber -local loadstring = loadstring -local gMatch = string.gmatch -local tInsert = table.insert -local sFind = string.find -local format = string.format - -local type = type -local pairs = pairs -local assert = assert +local tConcat = table.concat +local wipe = wipe + +local tonumber = tonumber +local tostring = tostring +local loadstring = loadstring +local gMatch = string.gmatch +local tInsert = table.insert +local sFind = string.find +local format = string.format +local f = string.format + +local type = type +local pairs = pairs +local assert = assert function Database.Init() local startTotal = 0 @@ -214,30 +217,40 @@ function Database.Override(overrideData, overrideTable, keys) end ---Used to add new Ids into the master list of ids for that type ----@param AllIdStrings string[] @A list of strings containing the ids in the database, will be concatinated into one string ----@param dataOverride table @The data to check for new ids ----@return QuestId[]|NpcId[]|ObjectId[]|ItemId[] @Returns a list of new ids -function Database.GetNewIds(AllIdStrings, dataOverride) - -- We add , to the start and end of the string so we can search for ,id, in the string - local allIds = "," .. tConcat(AllIdStrings, ",") .. "," - - -- Table to store the new ids - local newIds = {} - - -- Add all the ids to the allIds table - for id in pairs(dataOverride) do - -- Search in the idString if ,id, is found - -- local found, e, d = allIds:find("(,*" .. id .. ",*)") - local found = sFind(allIds, "," .. id .. ",") - if not found then - -- Print what we found - if not Database.debugLoadStaticEnabled and Database.debugPrintEnabled and Database.debugEnabled then - LibQuestieDB.ColorizePrint("reputationBlue", " Adding new ID", id) +do + -- Table used to store the ids for the current call + local allIdsSet = {} + ---Used to add new Ids into the master list of ids for that type + ---@param AllIdStrings string @A list of strings containing the ids in the database, will be concatinated into one string + ---@param dataOverride table @The data to check for new ids + ---@return QuestId[]|NpcId[]|ObjectId[]|ItemId[] @Returns a list of new ids + function Database.GetNewIds(AllIdStrings, dataOverride) + -- Split the string into a table of strings + local allIds = strsplittable(",", AllIdStrings) + + -- Create a hash set for all existing IDs + for i = 1, #allIds do + allIdsSet[allIds[i]] = true + end + + -- Table to store the new ids + local newIds = {} + -- Add all the ids to the allIds table + for id in pairs(dataOverride) do + if not allIdsSet[tostring(id)] then + -- Print what we found + -- if not Database.debugLoadStaticEnabled and Database.debugPrintEnabled and Database.debugEnabled then + -- LibQuestieDB.ColorizePrint("reputationBlue", " Adding new ID", id) + -- end + tInsert(newIds, id) end - tInsert(newIds, id) end + + -- Wipe it for the next call + -- Do it at the end so when it is called the last time it is wiped. + wipe(allIdsSet) + return newIds end - return newIds end --*------------------------------------------- diff --git a/Database/l10n/l10n.lua b/Database/l10n/l10n.lua index d50ee3dc..badcb5ae 100644 --- a/Database/l10n/l10n.lua +++ b/Database/l10n/l10n.lua @@ -9,9 +9,7 @@ local l10n = LibQuestieDB.CreateDatabaseInTable(LibQuestieDB.l10n, "l10n", {}) l10n.currentLocale = GetLocale() -- Set this to nil to use the locale of the client -- Override locale -l10n.currentLocale = "ptBR" - -GLOBl10n = l10n +-- l10n.currentLocale = "ptBR" -- Order Item, Npc, Object, Quest -- "enUS": "", # English (US) # Yes EN is empty @@ -45,20 +43,28 @@ local indexToLocale = { local localeToIndex = {} local localeToPattern = {} do + -- Populate the localeToIndex table with locale as key and its index as value + -- This helps in quickly finding the index of a given locale for k, v in pairs(indexToLocale) do localeToIndex[v] = k end + -- Function to create a pattern string for a given locale + -- This pattern is used to extract the localized string from a concatenated string of all locales local function createPatternForLocale(locale) - local patternString = "^" - local repeatPattern = ".-" - local capturePattern = "(.-)" + local patternString = "^" -- Start of the string + local repeatPattern = ".-" -- Non-greedy match for any character sequence + local capturePattern = "(.-)" -- Non-greedy match for any character sequence, to be captured for i = 1, localeToIndex[locale] do - patternString = patternString .. (i == localeToIndex[locale] and capturePattern or repeatPattern) .. (i == #indexToLocale and "$" or specialChar) + -- Append the appropriate pattern based on the current index + patternString = patternString .. (i == localeToIndex[locale] and capturePattern or repeatPattern) + patternString = patternString .. (i == #indexToLocale and "$" or specialChar) end return patternString end + -- Populate the localeToPattern table with locale as key and its pattern as value + -- This pattern is used to extract the localized string for the given locale for i = 1, #indexToLocale do localeToPattern[indexToLocale[i]] = createPatternForLocale(indexToLocale[i]) end diff --git a/Helpers/ThreadLib.lua b/Helpers/ThreadLib.lua index ff4c9192..52013a2b 100644 --- a/Helpers/ThreadLib.lua +++ b/Helpers/ThreadLib.lua @@ -35,13 +35,13 @@ function ThreadLib.Thread(threadFunction, delay, errorMessage, callbackFunction) local timer timer = newTicker(delay or 0, function() - if (coStatus(thread) == "suspended") then --It's faster not to lookup the value but instead have it here + if (coStatus(thread) == "suspended") then --It's faster not to lookup the value but instead have it here local success = coResume(thread) -- Something in the coroutine went wrong, print the error and stop the timer if not success then timer:Cancel(); end - elseif (coStatus(thread) == "dead") then --It's faster not to lookup the value but instead have it here + elseif (coStatus(thread) == "dead") then --It's faster not to lookup the value but instead have it here timer:Cancel(); if (callbackFunction) then callbackFunction() @@ -78,4 +78,6 @@ end ---@param delay integer @Anything below 0.05 is each frame function ThreadLib.ThreadSimple(threadFunction, delay) return ThreadLib.Thread(threadFunction, delay) -end \ No newline at end of file +end + +return ThreadLib diff --git a/LuaLS-annotations.md b/LuaLS-annotations.md new file mode 100644 index 00000000..82b23c54 --- /dev/null +++ b/LuaLS-annotations.md @@ -0,0 +1,964 @@ +LUA Type Annotations + +Add additional context to your code and type checking. + +Annotations are prefixed with `---`, like a Lua comment with one extra dash. + +Annotation Formatting + +Annotations support most of the markdown syntax. More specifically, you can use: + +- headings +- bold text +- italic text +- struckthrough text +- ordered list +- unordered list +- blockquote +- code +- code block +- horizontal rule +- link +- image + +There are many ways to add newlines to your annotations. The most bulletproof way is to simply add an extra line of just `---`, although this functions like a paragraph break, not a newline. + +The below methods can be added to the end of a line: + +- HTML `
` tag (recommended) +- `\n` newline escape character +- Two trailing spaces (may be removed by formatting tools) +- Markdown backslash `\` (not recommended) + +Referring to symbols + +As of v3.9.2, you can refer to symbols from your workspace in markdown descriptions using markdown links. Hovering the described value will show a hyperlink that, when clicked, will take you to where the symbol is defined. + +```lua +---@alias MyCustomType integer + +---Calculate a value using [my custom type](lua://MyCustomType) +function calculate(x) end +``` + +Tips + +- If you type `---` one line above a function, you will receive a suggested snippet that includes `@param` and `@return` annotations for each parameter and return value found in the function. + +Documenting Types + +Properly documenting types with the language server is very important and where a lot of the features and advantages are. Below is a list of all recognized Lua types: + +- `nil` +- `any` +- `boolean` +- `string` +- `number` +- `integer` +- `function` +- `table` +- `thread` +- `userdata` +- `lightuserdata` + +You can also simulate classes and fields and even create your own types. + +Adding a question mark (`?`) after a type like `boolean?` or `number?` is the same as saying `boolean|nil` or `number|nil`. This can be used to specify that something is either a specified type or `nil`. This can be very useful for function returns where a value or `nil` can be returned. + +Below is a list of how you can document more advanced types: + +Type | Document As: +--- | --- +Union Type | `TYPE_1 | TYPE_2` +Array | `VALUE_TYPE[]` +Tuple | `[VALUE_TYPE, VALUE_TYPE]` +Dictionary | `{ [string]: VALUE_TYPE }` +Key-Value Table | `table` +Table literal | `{ key1: VALUE_TYPE, key2: VALUE_TYPE }` +Function | `fun(PARAM: TYPE): RETURN_TYPE` + +Unions may need to be placed in parentheses in certain situations, such as when defining an array that contains multiple value types: + +```Lua +---@type (string | integer)[] +local myArray = {} +``` + +Understanding This Page + +To get an understanding of how to use the annotations described on this page, you'll need to know how to read the Syntax sections of each annotation. + +Symbol | Meaning +--- | --- +`` | A required value that you provide +`[value_name]` | Everything inside is optional +`[value_name...]` | This value is repeatable +`value_name | value_name` | The left or right side are valid + +Any other symbols are syntactically required and should be copied verbatim. + +Annotations List + +Below is a list of all annotations recognized by the language server: + +@alias + +An alias can be used to create your own type. You can also use it to create an enum that does not exist at runtime. + +Syntax + +`---@alias ` + +Examples + +Simple alias + +```Lua +---@alias userID integer The ID of a user +``` + +Custom Type + +```Lua +---@alias modes "r" | "w" +``` + +Custom Type with Descriptions + +```Lua +---@alias DeviceSide +---| '"left"' # The left side of the device +---| '"right"' # The right side of the device +---| '"top"' # The top side of the device +---| '"bottom"' # The bottom side of the device +---| '"front"' # The front side of the device +---| '"back"' # The back side of the device + +---@param side DeviceSide +local function checkSide(side) end +``` + +Literal Custom Type + +```Lua +local A = "Hello" +local B = "World" + +---@alias myLiteralAlias `A` | `B` + +---@param x myLiteralAlias +function foo(x) end +``` + +@as + +Force a type onto an expression. + +Warning: This annotation cannot be added using `---@as ` it must be done like `--[[@as ]]`. + +Syntax + +`--[[@as ]]` + +Examples + +Override Type + +```Lua +---@param key string Must be a string +local function doSomething(key) end + +local x = nil + +doSomething(x --[[@as string]]) +``` + +@async + +Mark a function as being asynchronous. When hint.await is true, functions marked with @async will have an await hint displayed next to them when they are called. + +Syntax + +`---@async` + +Examples + +Mark Function Async + +```Lua +---@async +---Perform an asynchronous HTTP GET request +function http.get(url) end +``` + +@cast + +Cast a variable to a different type or types. + +Syntax + +`---@cast [+|-][, [+|-]...]` + +Examples + +Simple Cast + +```Lua +---@type integer | string +local x + +---@cast x string +print(x) --> x: string +``` + +Add Type + +```Lua +---@type integer +local x + +---@cast x +boolean +print(x) --> x: integer | boolean +``` + +Remove Type + +```Lua +---@type integer|string +local x + +---@cast x -integer +print(x) --> x: string +``` + +Cast Multiple Types + +```Lua +---@type string +local x --> x: string + +---@cast x +boolean, +number +print(x) --> x:string | boolean | number +``` + +Cast Possibly `nil` + +```Lua +---@type string +local x + +---@cast x +? +print(x) --> x:string? +``` + +@class + +Define a class. Can be used with @field to define a table structure. Once a class is defined, it can be used as a type for parameters, returns, and more. A class can also inherit one or more parent classes. Marking the class as (exact) means fields cannot be injected after the definition. + +Syntax + +`---@class [(exact)] [: [, ...]]` + +Examples + +Define a Class + +```Lua +---@class Car +local Car = {} +``` + +Class Inheritance + +```Lua +---@class Vehicle +local Vehicle = {} + +---@class Plane: Vehicle +local Plane = {} +``` + +Create exact class + +```Lua +---@class (exact) Point +---@field x number +---@field y number +local Point = {} +Point.x = 1 -- OK +Point.y = 2 -- OK +Point.z = 3 -- Warning +``` + +How the table Class is Implemented + +```Lua +---@class table: { [K]: V } +``` + +@deprecated + +Mark a function as deprecated. This will trigger the deprecated diagnostic, displaying it as struck through. + +Syntax + +`---@deprecated` + +Examples + +Mark a Function as Deprecated + +```Lua +---@deprecated +function outdated() end +``` + +@enum + +Mark a Lua table as an enum, giving it similar functionality to @alias, only the table is still usable at runtime. + +Syntax + +`---@enum [(key)] ` + +Examples + +Define Table as Enum + +```Lua +---@enum colors +local COLORS = { + black = 0, + red = 2, + green = 4, + yellow = 8, + blue = 16, + white = 32 +} + +---@param color colors +local function setColor(color) end + +setColor(COLORS.green) +``` + +Define Table Keys as Enum + +```Lua +---@enum (key) Direction +local direction = { + LEFT = 1, + RIGHT = 2, +} + +---@param dir Direction +local function move(dir) + assert(dir == "LEFT" or dir == "RIGHT") + + assert(direction[dir] == 1 or direction[dir] == 2) + assert(direction[dir] == direction.LEFT or direction[dir] == direction.RIGHT) +end + +move("LEFT") +``` + +@field + +Define a field within a table. Should be immediately following a @class. As of v3.6, you can mark a field as private, protected, public, or package. + +Syntax + +`---@field [scope] [description]` + +It is also possible to allow any key of a certain type to be added using the below: + +`---@field [scope] [] [description]` + +Examples + +Simple Documentation of a Class + +```Lua +---@class Person +---@field height number The height of this person in cm +---@field weight number The weight of this person in kg +---@field firstName string The first name of this person +---@field lastName? string The last name of this person +---@field age integer The age of this person + +---@param person Person +local function hire(person) end +``` + +Mark Field as Private + +```Lua +---@class Animal +---@field private legs integer +---@field eyes integer + +---@class Dog:Animal +local myDog = {} + +---Child class Dog CANNOT use private field legs +function myDog:legCount() + return self.legs +end +``` + +Mark Field as Protected + +```Lua +---@class Animal +---@field protected legs integer +---@field eyes integer + +---@class Dog:Animal +local myDog = {} + +---Child class Dog can use protected field legs +function myDog:legCount() + return self.legs +end +``` + +Typed Field + +```Lua +---@class Numbers +---@field named string +---@field [string] integer +local Numbers = {} +``` + +@generic + +Generics allow code to be reused and serve as a sort of "placeholder" for a type. + +Syntax + +`---@generic [:parent_type] [, [:parent_type]]` + +Examples + +Generic Function + +```Lua +---@generic T : integer +---@param p1 T +---@return T, T[] +function Generic(p1) end + +-- v1: string +-- v2: string[] +local v1, v2 = Generic("String") + +-- v3: integer +-- v4: integer[] +local v3, v4 = Generic(10) +``` + +Capture with Backticks + +```Lua +---@class Vehicle +local Vehicle = {} +function Vehicle:drive() end + +---@generic T +---@param class `T` # the type is captured using `T` +---@return T # generic type is returned +local function new(class) end + +-- obj: Vehicle +local obj = new("Vehicle") +``` + +Array Class Using Generics + +```Lua +---@class Array: { [integer]: T } + +---@type Array +local arr = {} + +-- Warns that I am assigning a boolean to a string +arr[1] = false + +arr[3] = "Correct" +``` + +Dictionary Class Using Generics + +```Lua +---@class Dictionary: { [string]: T } + +---@type Dictionary +local dict = {} + +-- no warning despite assigning a string +dict["foo"] = "bar?" + +dict["correct"] = true +``` + +@meta + +Marks a file as "meta", meaning it is used for definitions and not for its functional Lua code. It is used internally by the language server for defining the built-in Lua libraries. If you are writing your own definition files, you will probably want to include this annotation in them. + +Syntax + +`---@meta [name]` + +Examples + +Mark Meta File + +```Lua +---@meta [name] +``` + +@module + +Simulates require-ing a file. + +Syntax + +`---@module ''` + +Examples + +"Require" a File + +```Lua +---@module 'http' + +--The above provides the same as +require 'http' +--within the language server +``` + +"Require" a File and Assign to a Variable + +```Lua +---@module 'http' +local http + +--The above provides the same as +local http = require 'http' +--within the language server +``` + +@nodiscard + +Mark a function as having return values that cannot be ignored/discarded. This can help users understand how to use the function as if they do not capture the returns, a warning will be raised. + +Syntax + +`---@nodiscard` + +Examples + +Prevent Ignoring a Function's Returns + +```Lua +---@return string username +---@nodiscard +function getUsername() end +``` + +@operator + +Provides type declarations for an operator metamethod. + +Syntax + +`---@operator [(param_type)]:` + +Examples + +Declare `__add` Metamethod + +```Lua +---@class Vector +---@operator add(Vector): Vector + +---@type Vector +local v1 +---@type Vector +local v2 + +--> v3: Vector +local v3 = v1 + v2 +``` + +Declare Unary Minus Metamethod + +```Lua +---@class Passcode +---@operator unm:integer + +---@type Passcode +local pA + +local pB = -pA +--> integer +``` + +Declare `__call` Metamethod + +```Lua +---@class URL +---@operator call:string +local URL = {} +``` + +@overload + +Define an additional signature for a function. + +Syntax + +`---@overload fun([param: type[, param: type...]]): [return_value[,return_value]]` + +Examples + +Define Function Overload + +```Lua +---@param objectID integer The id of the object to remove +---@param whenOutOfView boolean Only remove the object when it is not visible +---@return boolean success If the object was successfully removed +---@overload fun(objectID: integer): boolean +local function removeObject(objectID, whenOutOfView) end +``` + +Define Class Call Signature + +```Lua +---@overload fun(a: string): boolean +local foo = setmetatable({}, { + __call = function(a) + print(a) + return true + end, +}) + +local bool = foo("myString") +``` + +@package + +Mark a function as private to the file it is defined in. A packaged function cannot be accessed from another file. + +Syntax + +`---@package` + +Examples + +Mark a Function as Private to a Package + +```Lua +---@class Animal +---@field private eyes integer +local Animal = {} + +---@package +---This cannot be accessed in another file +function Animal:eyesCount() + return self.eyes +end +``` + +@param + +Define a parameter/argument for a function. This tells the language server what types are expected and can help enforce types and provide completion. Putting a question mark (`?`) after the parameter name will mark it as optional, meaning `nil` is an accepted type. + +Syntax + +`---@param [description]` + +Examples + +Simple Function Parameter + +```Lua +---@param username string The name to set for this user +function setUsername(username) end +``` + +Parameter Union Type + +```Lua +---@param setting string The name of the setting +---@param value string|number|boolean The value of the setting +local function settings.set(setting, value) end +``` + +Optional Parameter + +```Lua +---@param role string The name of the role +---@param isActive? boolean If the role is currently active +---@return Role +function Role.new(role, isActive) end +``` + +Variable Number of Parameters + +```Lua +---@param index integer +---@param ... string Tags to add to this entry +local function addTags(index, ...) end +``` + +Generic Function Parameter + +```Lua +---@class Box + +---@generic T +---@param objectID integer The ID of the object to set the type of +---@param type `T` The type of object to set +---@return `T` object The object as a Lua object +local function setObjectType(objectID, type) end + +--> boxObject: Box +local boxObject = setObjectType(1, "Box") +``` + +Custom Type Parameter + +```Lua +---@param mode string +---|"'immediate'" # comment 1 +---|"'async'" # comment 2 +function bar(mode) end +``` + +Literal Custom Type Parameter + +```Lua +local A = 0 +local B = 1 + +---@param active integer +---| `A` # Has a value of 0 +---| `B` # Has a value of 1 +function set(active) end +``` + +@private + +Mark a function as private to a class. Private functions can be accessed only from within their class and are not accessible from child classes. + +Syntax + +`---@private` + +Examples + +Mark a function as private + +```Lua +---@class Animal +---@field private eyes integer +local Animal = {} + +---@private +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---NOT PERMITTED! +myDog:eyesCount(); +``` + +@protected + +Mark a function as protected within a class. Protected functions can be accessed only from within their class or from child classes. + +Syntax + +`---@protected` + +Examples + +Mark a function as protected + +```Lua +---@class Animal +---@field private eyes integer +local Animal = {} + +---@protected +function Animal:eyesCount() + return self.eyes +end + +---@class Dog:Animal +local myDog = {} + +---Permitted because function is protected, not private. +myDog:eyesCount(); +``` + +@return + +Define a return value for a function. This tells the language server what types are expected and can help enforce types and provide completion. + +Syntax + +`---@return [ [comment] | [name] #]` + +Examples + +Simple Function Return + +```Lua +---@return boolean +local function isEnabled() end +``` + +Named Function Return + +```Lua +---@return boolean enabled +local function isEnabled() end +``` + +Named, Described Function Return + +```Lua +---@return boolean enabled If the item is enabled +local function isEnabled() end +``` + +Described Function Return + +```Lua +---@return boolean # If the item is enabled +local function isEnabled() end +``` + +Optional Function Return + +```Lua +---@return boolean|nil error +local function makeRequest() end +``` + +Variable Function Returns + +```Lua +---@return integer count Number of nicknames found +---@return string ... +local function getNicknames() end +``` + +@type + +Mark a variable as being of a certain type. Union types are separated with a pipe character `|`. + +Syntax + +`---@type ` + +Examples + +Basic Type Definition + +```Lua +---@type boolean +local x +``` + +Array Type Definition + +```Lua +---@type string[] +local names +``` + +Dictionary Type Definition + +```Lua +---@type { [string]: boolean } +local statuses +``` + +Table Type Definition + +```Lua +---@type table +local players +``` + +Union Type Definition + +```Lua +---@type boolean|number|"yes"|"no" +local x +``` + +Function Type Definition + +```Lua +---@type fun(name: string, value: any): boolean +local x +``` + +@vararg + +Mark a function as having variable arguments. For variable returns, see @return. + +This annotation has been deprecated and is purely for legacy support for EmmyLua annotations. Use @param instead. + +Syntax + +`---@vararg ` + +Examples + +Basic Variable Function Arguments + +```Lua +---@vararg string +function concat(...) end +``` + +@version + +Mark the required Lua version for a function or @class. + +Syntax + +`---@version [<|>] [, [<|>]version...]` + +Possible version values: + +- `5.1` +- `5.2` +- `5.3` +- `5.4` +- `JIT` + +Examples + +Declare Function Version + +```Lua +---@version >5.2, JIT +function hello() end +``` + +Declare Class Version + +```Lua +---@version 5.4 +---@class Entry +``` \ No newline at end of file diff --git a/QuestieDB-BCC.toc b/QuestieDB-BCC.toc index 478b0f71..59cba44b 100644 --- a/QuestieDB-BCC.toc +++ b/QuestieDB-BCC.toc @@ -29,6 +29,8 @@ Database\Object\Tbc\ObjectDataFiles.xml Database\Quest\Tbc\QuestDataFiles.xml ## l10n Database\l10n\Tbc\L10nDataFiles.xml +## Translations +Translations\Translations.xml ##* Corrections Meta Corrections\MetaCorrections.xml diff --git a/QuestieDB-Classic.toc b/QuestieDB-Classic.toc index 0d974609..7118a5db 100644 --- a/QuestieDB-Classic.toc +++ b/QuestieDB-Classic.toc @@ -29,6 +29,8 @@ Database\Object\Era\ObjectDataFiles.xml Database\Quest\Era\QuestDataFiles.xml ## l10n Database\l10n\Era\L10nDataFiles.xml +## Translations +Translations\Translations.xml ##* Corrections Meta Corrections\MetaCorrections.xml diff --git a/QuestieDB-WOTLKC.toc b/QuestieDB-WOTLKC.toc index a3b5a67e..1bb85dcd 100644 --- a/QuestieDB-WOTLKC.toc +++ b/QuestieDB-WOTLKC.toc @@ -29,6 +29,8 @@ Database\Object\Wotlk\ObjectDataFiles.xml Database\Quest\Wotlk\QuestDataFiles.xml ## l10n Database\l10n\Wotlk\L10nDataFiles.xml +## Translations +Translations\Translations.xml ##* Corrections Meta Corrections\MetaCorrections.xml diff --git a/QuestieDB.lua b/QuestieDB.lua index 8420e0b4..5cb1c7bb 100644 --- a/QuestieDB.lua +++ b/QuestieDB.lua @@ -102,6 +102,8 @@ SlashCmdList["QuestieDB"] = function(args) print("Localized math.floor", time1, "ms") print("Modulus to floor", time2, "ms") end + collectgarbage() end + SLASH_QuestieDB1 = "/QuestieDB" SLASH_QuestieDB2 = "/qdb" diff --git a/Translations/_data/.gitkeep b/Translations/_data/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Translations/translations.xml b/Translations/translations.xml new file mode 100644 index 00000000..907213e9 --- /dev/null +++ b/Translations/translations.xml @@ -0,0 +1,4 @@ + +