使用 Gradle 自动更新文档内容 (#4403)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/// @author Glavo
|
||||
public record Document(DocumentFileTree directory,
|
||||
Path file,
|
||||
String name, DocumentLocale locale,
|
||||
List<Item> items) {
|
||||
|
||||
private static final Pattern MACRO_BEGIN = Pattern.compile(
|
||||
"<!-- #BEGIN (?<name>\\w+) -->"
|
||||
);
|
||||
|
||||
private static final Pattern MACRO_PROPERTY_LINE = Pattern.compile(
|
||||
"<!-- #PROPERTY (?<name>\\w+)=(?<value>.*) -->"
|
||||
);
|
||||
|
||||
private static String parsePropertyValue(String value) {
|
||||
int i = 0;
|
||||
while (i < value.length()) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\\')
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == value.length())
|
||||
return value;
|
||||
|
||||
StringBuilder builder = new StringBuilder(value.length());
|
||||
builder.append(value, 0, i);
|
||||
for (; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\\' && i < value.length() - 1) {
|
||||
char next = value.charAt(++i);
|
||||
switch (next) {
|
||||
case 'n' -> builder.append('\n');
|
||||
case 'r' -> builder.append('\r');
|
||||
case '\\' -> builder.append('\\');
|
||||
default -> builder.append(next);
|
||||
}
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static void writePropertyValue(StringBuilder builder, String value) {
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
|
||||
switch (ch) {
|
||||
case '\\' -> builder.append("\\\\");
|
||||
case '\r' -> builder.append("\\r");
|
||||
case '\n' -> builder.append("\\n");
|
||||
default -> builder.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Document load(DocumentFileTree directory, Path file, String name, DocumentLocale locale) throws IOException {
|
||||
var items = new ArrayList<Item>();
|
||||
try (var reader = Files.newBufferedReader(file)) {
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.startsWith("<!-- #")) {
|
||||
items.add(new Line(line));
|
||||
} else {
|
||||
Matcher matcher = MACRO_BEGIN.matcher(line);
|
||||
if (!matcher.matches())
|
||||
throw new IOException("Invalid macro begin line: " + line);
|
||||
|
||||
String macroName = matcher.group("name");
|
||||
String endLine = "<!-- #END " + macroName + " -->";
|
||||
var properties = new LinkedHashMap<String, List<String>>();
|
||||
var lines = new ArrayList<String>();
|
||||
|
||||
// Handle properties
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.startsWith("<!-- #") || line.equals(endLine))
|
||||
break;
|
||||
|
||||
Matcher propertyMatcher = MACRO_PROPERTY_LINE.matcher(line);
|
||||
if (propertyMatcher.matches()) {
|
||||
String propertyName = propertyMatcher.group("name");
|
||||
String propertyValue = parsePropertyValue(propertyMatcher.group("value"));
|
||||
|
||||
properties.computeIfAbsent(propertyName, k -> new ArrayList<>(1))
|
||||
.add(propertyValue);
|
||||
} else {
|
||||
throw new IOException("Invalid macro property line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle lines
|
||||
if (line != null && !line.equals(endLine)) {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("<!-- #"))
|
||||
break;
|
||||
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (line == null || !line.equals(endLine))
|
||||
throw new IOException("Invalid macro end line: " + line);
|
||||
|
||||
items.add(new MacroBlock(macroName,
|
||||
Collections.unmodifiableMap(properties),
|
||||
Collections.unmodifiableList(lines)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Document(directory, file, name, locale, items);
|
||||
}
|
||||
|
||||
public sealed interface Item {
|
||||
}
|
||||
|
||||
public record MacroBlock(String name, Map<String, List<String>> properties,
|
||||
List<String> contentLines) implements Item {
|
||||
}
|
||||
|
||||
public record Line(String content) implements Item {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/// @author Glavo
|
||||
public final class DocumentFileTree {
|
||||
|
||||
public static DocumentFileTree load(Path dir) throws IOException {
|
||||
Path documentsDir = dir.toRealPath();
|
||||
DocumentFileTree rootTree = new DocumentFileTree();
|
||||
|
||||
Files.walkFileTree(documentsDir, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
|
||||
String fileName = file.getFileName().toString();
|
||||
if (fileName.endsWith(".md")) {
|
||||
DocumentFileTree tree = rootTree.getFileTree(documentsDir.relativize(file.getParent()));
|
||||
if (tree == null)
|
||||
throw new AssertionError();
|
||||
|
||||
var result = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - ".md".length()));
|
||||
tree.getFiles().computeIfAbsent(result.name(), name -> new LocalizedDocument(tree, name))
|
||||
.getDocuments()
|
||||
.put(result.locale(), Document.load(tree, file, result.name(), result.locale()));
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
|
||||
return rootTree;
|
||||
}
|
||||
|
||||
private final @Nullable DocumentFileTree parent;
|
||||
private final TreeMap<String, DocumentFileTree> children = new TreeMap<>();
|
||||
private final TreeMap<String, LocalizedDocument> files = new TreeMap<>();
|
||||
|
||||
public DocumentFileTree() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public DocumentFileTree(@Nullable DocumentFileTree parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Nullable DocumentFileTree getFileTree(Path relativePath) {
|
||||
if (relativePath.isAbsolute())
|
||||
throw new IllegalArgumentException(relativePath + " is absolute");
|
||||
|
||||
if (relativePath.getNameCount() == 0)
|
||||
throw new IllegalArgumentException(relativePath + " is empty");
|
||||
|
||||
if (relativePath.getNameCount() == 1 && relativePath.getName(0).toString().isEmpty())
|
||||
return this;
|
||||
|
||||
DocumentFileTree current = this;
|
||||
for (int i = 0; i < relativePath.getNameCount(); i++) {
|
||||
String name = relativePath.getName(i).toString();
|
||||
if (name.isEmpty())
|
||||
throw new IllegalStateException(name + " is empty");
|
||||
else if (name.equals("."))
|
||||
continue;
|
||||
else if (name.equals("..")) {
|
||||
current = current.parent;
|
||||
if (current == null)
|
||||
return null;
|
||||
} else {
|
||||
DocumentFileTree finalCurrent = current;
|
||||
current = current.children.computeIfAbsent(name, ignored -> new DocumentFileTree(finalCurrent));
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public @Nullable DocumentFileTree getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public TreeMap<String, DocumentFileTree> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public TreeMap<String, LocalizedDocument> getFiles() {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/// @author Glavo
|
||||
public enum DocumentLocale {
|
||||
ENGLISH(Locale.ENGLISH, "") {
|
||||
@Override
|
||||
public List<DocumentLocale> getCandidates() {
|
||||
return List.of(ENGLISH);
|
||||
}
|
||||
},
|
||||
SIMPLIFIED_CHINESE(Locale.forLanguageTag("zh-Hans"), "zh"),
|
||||
TRADITIONAL_CHINESE("zh-Hant") {
|
||||
@Override
|
||||
public List<DocumentLocale> getCandidates() {
|
||||
return List.of(TRADITIONAL_CHINESE, SIMPLIFIED_CHINESE, ENGLISH);
|
||||
}
|
||||
},
|
||||
WENYAN("lzh") {
|
||||
@Override
|
||||
public String getLanguageDisplayName() {
|
||||
return TRADITIONAL_CHINESE.getLanguageDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubLanguageDisplayName() {
|
||||
return "文言";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DocumentLocale> getCandidates() {
|
||||
return List.of(WENYAN, TRADITIONAL_CHINESE, SIMPLIFIED_CHINESE, ENGLISH);
|
||||
}
|
||||
},
|
||||
JAPANESE("ja"),
|
||||
SPANISH("es"),
|
||||
RUSSIAN("ru"),
|
||||
UKRAINIAN("uk"),
|
||||
;
|
||||
|
||||
public record LocaleAndName(DocumentLocale locale, String name) {
|
||||
}
|
||||
|
||||
public static LocaleAndName parseFileName(String fileNameWithoutExtension) {
|
||||
for (DocumentLocale locale : values()) {
|
||||
String suffix = locale.getFileNameSuffix();
|
||||
if (suffix.isEmpty())
|
||||
continue;
|
||||
|
||||
if (fileNameWithoutExtension.endsWith(suffix))
|
||||
return new LocaleAndName(locale, fileNameWithoutExtension.substring(0, fileNameWithoutExtension.length() - locale.getFileNameSuffix().length()));
|
||||
}
|
||||
return new LocaleAndName(ENGLISH, fileNameWithoutExtension);
|
||||
}
|
||||
|
||||
private final Locale locale;
|
||||
private final String languageTag;
|
||||
private final String fileNameSuffix;
|
||||
|
||||
DocumentLocale(String languageTag) {
|
||||
this(Locale.forLanguageTag(languageTag), languageTag);
|
||||
}
|
||||
|
||||
DocumentLocale(Locale locale, String languageTag) {
|
||||
this.locale = locale;
|
||||
this.languageTag = languageTag;
|
||||
this.fileNameSuffix = languageTag.isEmpty() ? "" : "_" + languageTag.replace('-', '_');
|
||||
}
|
||||
|
||||
public String getLanguageDisplayName() {
|
||||
return locale.getDisplayLanguage(locale);
|
||||
}
|
||||
|
||||
public String getSubLanguageDisplayName() {
|
||||
boolean hasScript = !locale.getScript().isEmpty();
|
||||
boolean hasRegion = !locale.getCountry().isEmpty();
|
||||
|
||||
if (hasScript && hasRegion)
|
||||
throw new AssertionError("Unsupported locale: " + locale);
|
||||
|
||||
if (hasScript)
|
||||
return locale.getDisplayScript(locale);
|
||||
if (hasRegion)
|
||||
return locale.getDisplayCountry(locale);
|
||||
return "";
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public String getFileNameSuffix() {
|
||||
return fileNameSuffix;
|
||||
}
|
||||
|
||||
public List<DocumentLocale> getCandidates() {
|
||||
return List.of(this, ENGLISH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
/// @author Glavo
|
||||
public final class LocalizedDocument {
|
||||
private final DocumentFileTree directory;
|
||||
private final String name;
|
||||
private final EnumMap<DocumentLocale, Document> documents = new EnumMap<>(DocumentLocale.class);
|
||||
|
||||
public LocalizedDocument(DocumentFileTree directory, String name) {
|
||||
this.directory = directory;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DocumentFileTree getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public EnumMap<DocumentLocale, Document> getDocuments() {
|
||||
return documents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof LocalizedDocument that
|
||||
&& this.documents.equals(that.documents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return documents.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalizedDocument[" +
|
||||
"files=" + documents + ']';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import javax.print.Doc;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/// @author Glavo
|
||||
public enum MacroProcessor {
|
||||
LANGUAGE_SWITCHER {
|
||||
private static <T> boolean containsIdentity(List<T> list, T element) {
|
||||
for (T t : list) {
|
||||
if (t == element)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Document document,
|
||||
Document.MacroBlock macroBlock,
|
||||
StringBuilder outputBuilder) throws IOException {
|
||||
LocalizedDocument localized = document.directory().getFiles().get(document.name());
|
||||
if (localized == null || localized.getDocuments().isEmpty())
|
||||
throw new AssertionError("Document " + document.name() + " does not exist");
|
||||
|
||||
MacroProcessor.writeBegin(outputBuilder, macroBlock);
|
||||
if (localized.getDocuments().size() > 1) {
|
||||
var languageToDocs = new LinkedHashMap<String, List<Document>>();
|
||||
for (DocumentLocale locale : DocumentLocale.values()) {
|
||||
Document targetDoc = localized.getDocuments().get(locale);
|
||||
if (targetDoc != null) {
|
||||
languageToDocs.computeIfAbsent(locale.getLanguageDisplayName(), name -> new ArrayList<>(1))
|
||||
.add(targetDoc);
|
||||
}
|
||||
}
|
||||
|
||||
boolean firstLanguage = true;
|
||||
|
||||
for (var entry : languageToDocs.entrySet()) {
|
||||
if (firstLanguage)
|
||||
firstLanguage = false;
|
||||
else
|
||||
outputBuilder.append(" | ");
|
||||
|
||||
String languageName = entry.getKey();
|
||||
List<Document> targetDocs = entry.getValue();
|
||||
|
||||
boolean containsCurrent = containsIdentity(targetDocs, document);
|
||||
if (targetDocs.size() == 1) {
|
||||
if (containsCurrent)
|
||||
outputBuilder.append("**").append(languageName).append("**");
|
||||
else
|
||||
outputBuilder.append("[").append(languageName).append("](").append(targetDocs.get(0).file().getFileName()).append(")");
|
||||
} else {
|
||||
if (containsCurrent)
|
||||
outputBuilder.append("**").append(languageName).append("**");
|
||||
else
|
||||
outputBuilder.append(languageName);
|
||||
|
||||
outputBuilder.append(" (");
|
||||
|
||||
boolean isFirst = true;
|
||||
for (Document targetDoc : targetDocs) {
|
||||
if (isFirst)
|
||||
isFirst = false;
|
||||
else
|
||||
outputBuilder.append(", ");
|
||||
|
||||
String subLanguage = targetDoc.locale().getSubLanguageDisplayName();
|
||||
|
||||
if (targetDoc == document) {
|
||||
outputBuilder.append("**").append(subLanguage).append("**");
|
||||
} else {
|
||||
outputBuilder.append('[').append(subLanguage).append("](").append(targetDoc.file().getFileName()).append(")");
|
||||
}
|
||||
}
|
||||
|
||||
outputBuilder.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
outputBuilder.append('\n');
|
||||
}
|
||||
MacroProcessor.writeEnd(outputBuilder, macroBlock);
|
||||
}
|
||||
},
|
||||
BLOCK {
|
||||
@Override
|
||||
public void apply(Document document, Document.MacroBlock macroBlock, StringBuilder outputBuilder) throws IOException {
|
||||
MacroProcessor.writeBegin(outputBuilder, macroBlock);
|
||||
MacroProcessor.writeProperties(outputBuilder, macroBlock);
|
||||
for (String line : macroBlock.contentLines()) {
|
||||
outputBuilder.append(line).append('\n');
|
||||
}
|
||||
MacroProcessor.writeEnd(outputBuilder, macroBlock);
|
||||
}
|
||||
};
|
||||
|
||||
private static void writeBegin(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {
|
||||
builder.append("<!-- #BEGIN ");
|
||||
builder.append(macroBlock.name());
|
||||
builder.append(" -->\n");
|
||||
}
|
||||
|
||||
private static void writeEnd(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {
|
||||
builder.append("<!-- #END ");
|
||||
builder.append(macroBlock.name());
|
||||
builder.append(" -->\n");
|
||||
}
|
||||
|
||||
private static void writeProperties(StringBuilder builder, Document.MacroBlock macroBlock) throws IOException {
|
||||
macroBlock.properties().forEach((key, values) -> {
|
||||
for (String value : values) {
|
||||
builder.append("<!-- #PROPERTY ").append(key).append('=');
|
||||
Document.writePropertyValue(builder, value);
|
||||
builder.append(" -->\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void apply(Document document,
|
||||
Document.MacroBlock macroBlock,
|
||||
StringBuilder outputBuilder) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.gradle.docs;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.tasks.InputDirectory;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/// @author Glavo
|
||||
public abstract class UpdateDocuments extends DefaultTask {
|
||||
|
||||
@InputDirectory
|
||||
public abstract DirectoryProperty getDocumentsDir();
|
||||
|
||||
// ---
|
||||
|
||||
private static final Pattern LINK_PATTERN = Pattern.compile(
|
||||
"(?<=]\\()[a-zA-Z0-9_\\-./]+\\.md(?=\\))"
|
||||
);
|
||||
|
||||
private void processLine(StringBuilder outputBuilder, String line, Document document) {
|
||||
outputBuilder.append(LINK_PATTERN.matcher(line).replaceAll(matchResult -> {
|
||||
String rawLink = matchResult.group();
|
||||
String[] splitPath = rawLink.split("/");
|
||||
|
||||
if (splitPath.length == 0)
|
||||
return rawLink;
|
||||
|
||||
String fileName = splitPath[splitPath.length - 1];
|
||||
if (!fileName.endsWith(".md"))
|
||||
return rawLink;
|
||||
|
||||
DocumentFileTree current = document.directory();
|
||||
for (int i = 0; i < splitPath.length - 1; i++) {
|
||||
String name = splitPath[i];
|
||||
switch (name) {
|
||||
case "" -> {
|
||||
return rawLink;
|
||||
}
|
||||
case "." -> {
|
||||
continue;
|
||||
}
|
||||
case ".." -> {
|
||||
current = current.getParent();
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
default -> {
|
||||
current = current.getChildren().get(name);
|
||||
if (current == null)
|
||||
return rawLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DocumentLocale.LocaleAndName currentLocaleAndName = DocumentLocale.parseFileName(fileName.substring(0, fileName.length() - ".md".length()));
|
||||
LocalizedDocument localizedDocument = current.getFiles().get(currentLocaleAndName.name());
|
||||
if (localizedDocument != null) {
|
||||
List<DocumentLocale> candidateLocales = document.locale().getCandidates();
|
||||
for (DocumentLocale candidateLocale : candidateLocales) {
|
||||
if (candidateLocale == currentLocaleAndName.locale())
|
||||
return rawLink;
|
||||
|
||||
Document targetDoc = localizedDocument.getDocuments().get(candidateLocale);
|
||||
if (targetDoc != null) {
|
||||
splitPath[splitPath.length - 1] = targetDoc.file().getFileName().toString();
|
||||
return String.join("/", splitPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rawLink;
|
||||
})).append('\n');
|
||||
}
|
||||
|
||||
private void updateDocument(Document document) throws IOException {
|
||||
StringBuilder outputBuilder = new StringBuilder(8192);
|
||||
|
||||
for (Document.Item item : document.items()) {
|
||||
if (item instanceof Document.Line line) {
|
||||
processLine(outputBuilder, line.content(), document);
|
||||
} else if (item instanceof Document.MacroBlock macro) {
|
||||
var processor = MacroProcessor.valueOf(macro.name());
|
||||
processor.apply(document, macro, outputBuilder);
|
||||
} else
|
||||
throw new IllegalArgumentException("Unknown item type: " + item.getClass());
|
||||
}
|
||||
|
||||
Files.writeString(document.file(), outputBuilder.toString());
|
||||
}
|
||||
|
||||
private void processDocuments(DocumentFileTree tree) throws IOException {
|
||||
for (LocalizedDocument localizedDocument : tree.getFiles().values()) {
|
||||
for (Document document : localizedDocument.getDocuments().values()) {
|
||||
updateDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
for (DocumentFileTree subTree : tree.getChildren().values()) {
|
||||
processDocuments(subTree);
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
Path rootDir = getDocumentsDir().get().getAsFile().toPath();
|
||||
processDocuments(DocumentFileTree.load(rootDir));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user