Generate handbooks on every launch, fix html docs

This commit is contained in:
AnimeGitB
2022-08-03 17:11:04 +09:30
committed by Luke H-W
Unverified
parent e963419956
commit c1ff7332fe
5 changed files with 176 additions and 185 deletions
@@ -1,9 +1,7 @@
package emu.grasscutter.server.http.documentation;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.GameData;
@@ -12,112 +10,129 @@ import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils;
import express.http.Request;
import express.http.Response;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
final class HandbookRequestHandler implements DocumentationHandler {
private List<String> handbookHtmls;
private final String template;
private Map<Long, String> map;
public HandbookRequestHandler() {
final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html")));
if (templateFile.exists()) {
template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8);
this.template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8);
this.handbookHtmls = generateHandbookHtmls();
} else {
Grasscutter.getLogger().warn("File does not exist: " + templateFile);
template = null;
}
final String textMapFile = "TextMap/TextMap" + DOCUMENT_LANGUAGE + ".json";
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(
Utils.toFilePath(RESOURCE(textMapFile))), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory()
.fromJson(fileReader, new TypeToken<Map<Long, String>>() {
}.getType());
} catch (IOException e) {
Grasscutter.getLogger().warn("Resource does not exist: " + textMapFile);
map = new HashMap<>();
this.template = null;
}
}
@Override
public void handle(Request request, Response response) {
final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow
if (template == null) {
response.status(500);
return;
} else {
response.send(handbookHtmls.get(langIdx));
}
}
final CommandMap cmdMap = new CommandMap(true);
private List<String> generateHandbookHtmls() {
final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final List<String> output = new ArrayList<>(NUM_LANGUAGES);
final List<Language> languages = Language.TextStrings.getLanguages();
final List<StringBuilder> sbs = new ArrayList<>(NUM_LANGUAGES);
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.add(new StringBuilder(""));
// Commands table
new CommandMap(true).getHandlersAsList().forEach(cmd -> {
String label = cmd.getLabel();
String descKey = cmd.getDescriptionKey();
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + label + "</code></td><td>" + languages.get(langIdx).get(descKey) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> cmdsTable = sbs.stream().map(StringBuilder::toString).toList();
// Avatars table
final Int2ObjectMap<AvatarData> avatarMap = GameData.getAvatarDataMap();
sbs.forEach(sb -> sb.setLength(0));
avatarMap.keySet().intStream().sorted().mapToObj(avatarMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> avatarsTable = sbs.stream().map(StringBuilder::toString).toList();
// Items table
final Int2ObjectMap<ItemData> itemMap = GameData.getItemDataMap();
sbs.forEach(sb -> sb.setLength(0));
itemMap.keySet().intStream().sorted().mapToObj(itemMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> itemsTable = sbs.stream().map(StringBuilder::toString).toList();
// Scenes table
final Int2ObjectMap<SceneData> sceneMap = GameData.getSceneDataMap();
sceneMap.keySet().intStream().sorted().mapToObj(sceneMap::get).forEach(data -> {
int id = data.getId();
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + data.getScriptData() + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> scenesTable = sbs.stream().map(StringBuilder::toString).toList();
// Monsters table
final Int2ObjectMap<MonsterData> monsterMap = GameData.getMonsterDataMap();
monsterMap.keySet().intStream().sorted().mapToObj(monsterMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> monstersTable = sbs.stream().map(StringBuilder::toString).toList();
// Add translated title etc. to the page.
String content = template.replace("{{TITLE}}", translate("documentation.handbook.title"))
.replace("{{TITLE_COMMANDS}}", translate("documentation.handbook.title_commands"))
.replace("{{TITLE_AVATARS}}", translate("documentation.handbook.title_avatars"))
.replace("{{TITLE_ITEMS}}", translate("documentation.handbook.title_items"))
.replace("{{TITLE_SCENES}}", translate("documentation.handbook.title_scenes"))
.replace("{{TITLE_MONSTERS}}", translate("documentation.handbook.title_monsters"))
.replace("{{HEADER_ID}}", translate("documentation.handbook.header_id"))
.replace("{{HEADER_COMMAND}}", translate("documentation.handbook.header_command"))
.replace("{{HEADER_DESCRIPTION}}",
translate("documentation.handbook.header_description"))
.replace("{{HEADER_AVATAR}}", translate("documentation.handbook.header_avatar"))
.replace("{{HEADER_ITEM}}", translate("documentation.handbook.header_item"))
.replace("{{HEADER_SCENE}}", translate("documentation.handbook.header_scene"))
.replace("{{HEADER_MONSTER}}", translate("documentation.handbook.header_monster"))
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
Language lang = languages.get(langIdx);
output.add(template
.replace("{{TITLE}}", lang.get("documentation.handbook.title"))
.replace("{{TITLE_COMMANDS}}", lang.get("documentation.handbook.title_commands"))
.replace("{{TITLE_AVATARS}}", lang.get("documentation.handbook.title_avatars"))
.replace("{{TITLE_ITEMS}}", lang.get("documentation.handbook.title_items"))
.replace("{{TITLE_SCENES}}", lang.get("documentation.handbook.title_scenes"))
.replace("{{TITLE_MONSTERS}}", lang.get("documentation.handbook.title_monsters"))
.replace("{{HEADER_ID}}", lang.get("documentation.handbook.header_id"))
.replace("{{HEADER_COMMAND}}", lang.get("documentation.handbook.header_command"))
.replace("{{HEADER_DESCRIPTION}}", lang.get("documentation.handbook.header_description"))
.replace("{{HEADER_AVATAR}}", lang.get("documentation.handbook.header_avatar"))
.replace("{{HEADER_ITEM}}", lang.get("documentation.handbook.header_item"))
.replace("{{HEADER_SCENE}}", lang.get("documentation.handbook.header_scene"))
.replace("{{HEADER_MONSTER}}", lang.get("documentation.handbook.header_monster"))
// Commands table
.replace("{{COMMANDS_TABLE}}", cmdMap.getHandlersAsList()
.stream()
.map(cmd -> "<tr><td><code>" + cmd.getLabel() + "</code></td><td>" +
cmd.getDescriptionString(null) + "</td></tr>")
.collect(Collectors.joining("\n")))
// Avatars table
.replace("{{AVATARS_TABLE}}", GameData.getAvatarDataMap().keySet()
.intStream()
.sorted()
.mapToObj(avatarMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")))
// Items table
.replace("{{ITEMS_TABLE}}", GameData.getItemDataMap().keySet()
.intStream()
.sorted()
.mapToObj(itemMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")))
// Scenes table
.replace("{{SCENES_TABLE}}", GameData.getSceneDataMap().keySet()
.intStream()
.sorted()
.mapToObj(sceneMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
data.getScriptData() + "</td></tr>")
.collect(Collectors.joining("\n")))
.replace("{{MONSTERS_TABLE}}", GameData.getMonsterDataMap().keySet()
.intStream()
.sorted()
.mapToObj(monsterMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")));
response.send(content);
.replace("{{COMMANDS_TABLE}}", cmdsTable.get(langIdx))
.replace("{{AVATARS_TABLE}}", avatarsTable.get(langIdx))
.replace("{{ITEMS_TABLE}}", itemsTable.get(langIdx))
.replace("{{SCENES_TABLE}}", scenesTable.get(langIdx))
.replace("{{MONSTERS_TABLE}}", monstersTable.get(langIdx))
);
}
return output;
}
}