From 20b8f2ac934ead40827d7bb2a621563211dfa8ae Mon Sep 17 00:00:00 2001 From: Moderocky Date: Fri, 28 Feb 2025 03:59:54 +0000 Subject: [PATCH] Documentation annotations improvements (Prototype). (#7470) * Add example annotation. * Support new annotation in documentation generators. * Update src/main/java/ch/njol/skript/doc/Example.java --- .../ch/njol/skript/doc/Documentation.java | 7 ++- src/main/java/ch/njol/skript/doc/Example.java | 50 +++++++++++++++++++ .../ch/njol/skript/doc/HTMLGenerator.java | 29 +++++++++-- .../ch/njol/skript/doc/JSONGenerator.java | 17 +++++-- 4 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/njol/skript/doc/Example.java diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java index d1509f84a42..66386594cfc 100644 --- a/src/main/java/ch/njol/skript/doc/Documentation.java +++ b/src/main/java/ch/njol/skript/doc/Documentation.java @@ -286,7 +286,12 @@ private static void insertSyntaxElement(final PrintWriter pw, final SyntaxElemen Class elementClass = info.getElementClass(); if (elementClass.getAnnotation(NoDoc.class) != null) return; - if (elementClass.getAnnotation(Name.class) == null || elementClass.getAnnotation(Description.class) == null || elementClass.getAnnotation(Examples.class) == null || elementClass.getAnnotation(Since.class) == null) { + if (elementClass.getAnnotation(Name.class) == null + || elementClass.getAnnotation(Description.class) == null + || (!elementClass.isAnnotationPresent(Examples.class) + && !elementClass.isAnnotationPresent(Example.class) + && !elementClass.isAnnotationPresent(Example.Examples.class)) + || elementClass.getAnnotation(Since.class) == null) { Skript.warning("" + elementClass.getSimpleName() + " is missing information"); return; } diff --git a/src/main/java/ch/njol/skript/doc/Example.java b/src/main/java/ch/njol/skript/doc/Example.java new file mode 100644 index 00000000000..31934380c25 --- /dev/null +++ b/src/main/java/ch/njol/skript/doc/Example.java @@ -0,0 +1,50 @@ +package ch.njol.skript.doc; + +import java.lang.annotation.*; + +/** + * An example to be used in documentation for the annotated element. + * Multiple example annotations can be stacked on a single syntax element. + *

+ * Each annotation should include a single example. + * This can be used instead of the existing {@link ch.njol.skript.doc.Examples} annotation. + *

+ * Multi-line examples should use multi-line strings. + * Note that whitespace and quotes do not need to be escaped in this mode. + * The indentation will start from the least-indented line (and most IDEs provide a guideline to show this). + *

{@code
+ * @Example("set player's health to 1")
+ * @Example("""
+ * 		if player's health is greater than 10:
+ * 			send "Wow you're really healthy!"
+ * 		""")
+ * @Example("""
+ * 		# sets the player's health to 1
+ * 		set player's health to 1""")
+ * public class MyExpression extends ... {
+ * }
+ * }
+ */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Example.Examples.class) +@Documented +public @interface Example { + + String value(); + + boolean inTrigger() default true; // todo needed? + + /** + * The internal container annotation for multiple examples. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface Examples { + + Example[] value(); + + } + +} diff --git a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java index a2ef14fce7e..15a179a2594 100644 --- a/src/main/java/ch/njol/skript/doc/HTMLGenerator.java +++ b/src/main/java/ch/njol/skript/doc/HTMLGenerator.java @@ -20,6 +20,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.block.BlockCanBuildEvent; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryData; import org.skriptlang.skript.lang.entry.EntryValidator; @@ -442,10 +443,7 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); // Examples - Examples examples = c.getAnnotation(Examples.class); - desc = desc.replace("${element.examples}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples."))); - desc = desc.replace("${element.examples-safe}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples.value()) : null), "Missing examples.")) - .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + desc = extractExamples(desc, c); // Documentation ID desc = desc.replace("${element.id}", DocumentationIdProvider.getId(info)); @@ -546,6 +544,29 @@ private String generateAnnotated(String descTemp, SyntaxElementInfo info, @Nu return desc; } + private @NotNull String extractExamples(String desc, Class syntax) { + if (syntax.isAnnotationPresent(Example.class)) { + Example examples = syntax.getAnnotation(Example.class); + return this.addExamples(desc, examples.value()); + } else if (syntax.isAnnotationPresent(Example.Examples.class)) { + Example.Examples examples = syntax.getAnnotation(Example.Examples.class); + return this.addExamples(desc, Arrays.stream(examples.value()) + .map(Example::value).toArray(String[]::new)); + } else if (syntax.isAnnotationPresent(Examples.class)) { + Examples examples = syntax.getAnnotation(Examples.class); + return this.addExamples(desc, examples.value()); + } else { + return this.addExamples(desc, (String[]) null); + } + } + + private @NotNull String addExamples(String desc, String @Nullable ... examples) { + desc = desc.replace("${element.examples}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples) : null), "Missing examples."))); + desc = desc.replace("${element.examples-safe}", Joiner.on("
").join(getDefaultIfNullOrEmpty((examples != null ? Documentation.escapeHTML(examples) : null), "Missing examples.")) + .replace("\\", "\\\\").replace("\"", "\\\"").replace("\t", " ")); + return desc; + } + private String generateEvent(String descTemp, SkriptEventInfo info, @Nullable String page) { Class c = info.getElementClass(); String desc; diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java index f8c9827474e..0ef57d40822 100644 --- a/src/main/java/ch/njol/skript/doc/JSONGenerator.java +++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java @@ -13,6 +13,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -21,6 +22,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.Iterator; import java.util.Objects; import java.util.stream.Stream; @@ -39,7 +41,7 @@ public JSONGenerator(File templateDir, File outputDir) { * @param strings the String array to convert * @return the JsonArray containing the Strings */ - private static @Nullable JsonArray convertToJsonArray(String @Nullable [] strings) { + private static @Nullable JsonArray convertToJsonArray(String @Nullable ... strings) { if (strings == null) return null; JsonArray jsonArray = new JsonArray(); @@ -73,9 +75,18 @@ public JSONGenerator(File templateDir, File outputDir) { syntaxJsonObject.add("description", new JsonArray()); } - Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class); - if (examplesAnnotation != null) { + if (syntaxClass.isAnnotationPresent(Examples.class)) { + @NotNull Examples examplesAnnotation = syntaxClass.getAnnotation(Examples.class); syntaxJsonObject.add("examples", convertToJsonArray(examplesAnnotation.value())); + } else if (syntaxClass.isAnnotationPresent(Example.Examples.class)) { + // If there are multiple examples, they get containerised + @NotNull Example.Examples examplesAnnotation = syntaxClass.getAnnotation(Example.Examples.class); + syntaxJsonObject.add("examples", convertToJsonArray(Arrays.stream(examplesAnnotation.value()) + .map(Example::value).toArray(String[]::new))); + } else if (syntaxClass.isAnnotationPresent(Example.class)) { + // If the user adds just one example, it isn't containerised + @NotNull Example example = syntaxClass.getAnnotation(Example.class); + syntaxJsonObject.add("examples", convertToJsonArray(example.value())); } else { syntaxJsonObject.add("examples", new JsonArray()); }