diff --git a/.gitignore b/.gitignore index 2e7f557d3..ec436e630 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # gradle +.kotlin/ .gradle/ build/ out/ @@ -42,5 +43,10 @@ docs/web/[0-9]* *.af~lock~ *.af~lock~:com.dropbox.ignored +# Vitepress docgen +node_modules/ +**/.vitepress/cache/ +**/.vitepress/dist/ + # General working dir for anything that is needed during development working/ diff --git a/build.gradle.kts b/build.gradle.kts index ddbbe9273..522f206f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,6 +90,14 @@ val distDirFile = distDir.asFile val docsBuildDir = layout.buildDirectory.dir("docs").get().asFile val docletJarFile = layout.projectDirectory.file("buildSrc/build/libs/buildSrc.jar").asFile +repositories { + mavenCentral() +} + +val docletClasspath = configurations.detachedConfiguration( + dependencies.create("com.google.code.gson:gson:2.9.0") +) + // Root-level properties (available in root gradle.properties) val modIdProvider = providers.gradleProperty("mod_id") val channelProvider = providers.gradleProperty("channel").orElse("release") @@ -222,8 +230,8 @@ if (isVersionedProject && hasMinecraftVersion) { classpath = documentationClasspath dependsOn(minecraftArtifactTasks) destinationDir = File(docsBuildDir, "python/JsMacrosAC") - options.doclet = "com.jsmacrosce.doclet.pydoclet.Main" - options.docletpath = mutableListOf(docletJarFile) + options.doclet = "com.jsmacrosce.doclet.core.pydoclet.Main" + options.docletpath = (listOf(docletJarFile) + docletClasspath.files).toMutableList() (options as CoreJavadocOptions).addStringOption("v", project.version.toString()) } @@ -242,8 +250,8 @@ if (isVersionedProject && hasMinecraftVersion) { classpath = documentationClasspath dependsOn(minecraftArtifactTasks) destinationDir = File(docsBuildDir, "typescript/headers") - options.doclet = "com.jsmacrosce.doclet.tsdoclet.Main" - options.docletpath = mutableListOf(docletJarFile) + options.doclet = "com.jsmacrosce.doclet.core.tsdoclet.Main" + options.docletpath = (listOf(docletJarFile) + docletClasspath.files).toMutableList() (options as CoreJavadocOptions).addStringOption("v", project.version.toString()) } @@ -262,14 +270,14 @@ if (isVersionedProject && hasMinecraftVersion) { classpath = documentationClasspath dependsOn(minecraftArtifactTasks) destinationDir = File(docsBuildDir, "web") - options.doclet = "com.jsmacrosce.doclet.webdoclet.Main" - options.docletpath = mutableListOf(docletJarFile) + options.doclet = "com.jsmacrosce.doclet.core.webdoclet.Main" + options.docletpath = (listOf(docletJarFile) + docletClasspath.files).toMutableList() (options as CoreJavadocOptions).addStringOption("v", project.version.toString()) (options as CoreJavadocOptions).addStringOption("mcv", mcVersion) (options as StandardJavadocDocletOptions).links( "https://docs.oracle.com/javase/8/docs/api/", - "https://www.javadoc.io/doc/org.slf4j/slf4j-api/1.7.30/", - "https://javadoc.io/doc/com.neovisionaries/nv-websocket-client/latest/" + "https://www.slf4j.org/apidocs-2.0.17/", + "https://takahikokawasaki.github.io/nv-websocket-client/" ) } @@ -285,10 +293,41 @@ if (isVersionedProject && hasMinecraftVersion) { } } + tasks.register("generateVitepressDoc", Javadoc::class.java) { + group = "documentation" + description = "Generates the vitepress documentation for the project" + source(documentationSources) + classpath = documentationClasspath + dependsOn(minecraftArtifactTasks) + destinationDir = File(docsBuildDir, "vitepress") + options.doclet = "com.jsmacrosce.doclet.core.mddoclet.Main" + options.docletpath = (listOf(docletJarFile) + docletClasspath.files).toMutableList() + (options as CoreJavadocOptions).addStringOption("v", project.version.toString()) + (options as CoreJavadocOptions).addStringOption("mcv", mcVersion) + (options as StandardJavadocDocletOptions).links( + "https://docs.oracle.com/javase/8/docs/api/", + "https://www.slf4j.org/apidocs-2.0.17/", + "https://takahikokawasaki.github.io/nv-websocket-client/", + "https://www.graalvm.org/sdk/javadoc/" + ) + } + + tasks.register("copyVitepressDoc", Copy::class.java) { + group = "documentation" + description = "Copies the vitepress documentation to the build folder" + dependsOn("generateVitepressDoc") + from(rootProject.file("docs/vitepress")) + into(File(docsBuildDir, "vitepress")) + inputs.property("version", project.version.toString()) + filesMatching("index.md") { + expand(mapOf("version" to project.version.toString())) + } + } + tasks.register("createDistDocs", Copy::class.java) { group = "distribution" description = "Packages generated documentation into the dist directory" - dependsOn("prepareDist", "copyPyDoc", "copyTSDoc", "copyWebDoc") + dependsOn("prepareDist", "copyPyDoc", "copyTSDoc", "copyWebDoc", "copyVitepressDoc") from(docsBuildDir) into(distDirFile) } diff --git a/buildSrc/src/main/java/com/jsmacrosce/FileHandler.java b/buildSrc/src/main/java/com/jsmacrosce/FileHandler.java index 39cbc6f6c..debd9ace5 100644 --- a/buildSrc/src/main/java/com/jsmacrosce/FileHandler.java +++ b/buildSrc/src/main/java/com/jsmacrosce/FileHandler.java @@ -1,11 +1,14 @@ package com.jsmacrosce; +import com.jsmacrosce.doclet.DocletIgnore; + import java.io.*; /** * @author Wagyourtail * @since 1.1.8 */ +@DocletIgnore public class FileHandler { private final File f; @@ -53,15 +56,15 @@ public FileHandler write(byte[] b) throws IOException { * @since 1.1.8 */ public String read() throws IOException { - String ret = ""; + StringBuilder ret = new StringBuilder(); try (BufferedReader in = new BufferedReader(new FileReader(f))) { String line = in.readLine(); while (line != null) { - ret += line + "\n"; + ret.append(line).append("\n"); line = in.readLine(); } } - return ret; + return ret.toString(); } /** diff --git a/buildSrc/src/main/java/com/jsmacrosce/MarkdownBuilder.java b/buildSrc/src/main/java/com/jsmacrosce/MarkdownBuilder.java new file mode 100644 index 000000000..45a16b884 --- /dev/null +++ b/buildSrc/src/main/java/com/jsmacrosce/MarkdownBuilder.java @@ -0,0 +1,232 @@ +package com.jsmacrosce; + +import com.jsmacrosce.doclet.DocletIgnore; + +import java.util.Map; + +/** + * Fluent builder for Markdown documents with VitePress-specific constructs. + * + *
Block-level methods automatically manage blank lines between elements. + * Consecutive bullet items (and items that immediately follow a + * {@link #boldHeader}) are not separated by blank lines; a blank line is + * inserted before any other block element that follows a list. + * + *
Inline helpers ({@link #codeSpan} and {@link #link}) are static so they + * can be used in contexts where only a {@code String} is needed, such as + * inside {@code MarkdownWriter} signatures. + */ +@DocletIgnore +public class MarkdownBuilder { + + /** Tracks what the last emitted element was, to decide separators. */ + private enum State { + /** Nothing has been emitted yet. */ + START, + /** Last emitted element was a regular block (heading, paragraph, …). */ + BLOCK, + /** + * Last emitted element was a list item or a bold-header (a bold label + * immediately followed by list items, e.g. {@code **Parameters:**}). + * Consecutive list elements never receive a blank-line separator. + */ + LIST + } + + private final StringBuilder sb = new StringBuilder(); + private State state = State.START; + + // ----------------------------------------------------------------------- + // Inline helpers (static — no state, return String) + // ----------------------------------------------------------------------- + + /** + * Wraps {@code text} in backtick fences, choosing a fence length that does + * not conflict with any backtick run already inside {@code text}. + * + * @param text the raw text content; must not be {@code null} + * @return a markdown inline code span + */ + public static String codeSpan(String text) { + if (text == null) { + return ""; + } + int maxTicks = 0; + int current = 0; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == '`') { + current++; + if (current > maxTicks) { + maxTicks = current; + } + } else { + current = 0; + } + } + String fence = "`".repeat(maxTicks + 1); + return fence + text + fence; + } + + /** + * Returns a markdown inline link: {@code [text](url)}. + * + * @param text the link label + * @param url the link target + * @return a markdown link string + */ + public static String link(String text, String url) { + return "[" + text + "](" + url + ")"; + } + + // ----------------------------------------------------------------------- + // Block-level methods + // ----------------------------------------------------------------------- + + /** + * Emits a YAML frontmatter block at the current position. + * + *
Entries are written in insertion order. Values are emitted verbatim
+ * (no quoting is added by this method).
+ *
+ * @param entries key-value pairs for the frontmatter
+ * @return {@code this}
+ */
+ public MarkdownBuilder frontmatter(Map Example output: {@code **Parameters:**\n}. No blank line is inserted
+ * between this header and subsequent {@link #bulletItem} calls.
+ *
+ * @param label the bold label (without the {@code **} delimiters)
+ * @return {@code this}
+ */
+ public MarkdownBuilder boldHeader(String label) {
+ beginBlock();
+ sb.append("**").append(label).append("**\n");
+ state = State.LIST;
+ return this;
+ }
+
+ /**
+ * Emits a list item ({@code - text\n}).
+ *
+ * Consecutive bullet items are never separated by blank lines. A blank
+ * line is inserted before the first bullet item when it follows a
+ * non-list block (i.e. when the preceding state is {@link State#BLOCK}).
+ *
+ * @param text list-item content (may include inline markdown)
+ * @return {@code this}
+ */
+ public MarkdownBuilder bulletItem(String text) {
+ if (state == State.BLOCK) {
+ sb.append("\n");
+ }
+ sb.append("- ").append(text).append("\n");
+ state = State.LIST;
+ return this;
+ }
+
+ /**
+ * Appends {@code text} verbatim without inserting any separator or
+ * modifying the current state. Use this for pre-formatted content whose
+ * structural role is best managed by the caller.
+ *
+ * @param text raw text to append
+ * @return {@code this}
+ */
+ public MarkdownBuilder raw(String text) {
+ sb.append(text);
+ return this;
+ }
+
+ // -----------------------------------------------------------------------
+ // Rendering
+ // -----------------------------------------------------------------------
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ // Internal helpers
+ // -----------------------------------------------------------------------
+
+ /**
+ * Inserts a blank line before the upcoming block element when needed.
+ *
+ * A blank line is inserted in two situations:
+ *
+ *
+ * In the initial {@link State#START} state nothing is emitted.
+ */
+ private void beginBlock() {
+ if (state != State.START) {
+ sb.append("\n");
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/com/jsmacrosce/Pair.java b/buildSrc/src/main/java/com/jsmacrosce/Pair.java
index e1bbeff71..24c1a3455 100644
--- a/buildSrc/src/main/java/com/jsmacrosce/Pair.java
+++ b/buildSrc/src/main/java/com/jsmacrosce/Pair.java
@@ -1,5 +1,8 @@
package com.jsmacrosce;
+import com.jsmacrosce.doclet.DocletIgnore;
+
+@DocletIgnore
public class Pair