Skip to content

Commit 89b90fa

Browse files
committed
feat: ConfigValidator and PluginExtractor
1 parent ee1ab22 commit 89b90fa

File tree

5 files changed

+279
-54
lines changed

5 files changed

+279
-54
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<script setup lang="ts">
2+
import {onMounted, onUnmounted, ref} from 'vue';
3+
import {parse} from "yaml";
4+
import {createHighlighter} from 'shiki'
5+
import {HighlighterGeneric} from "@shikijs/types";
6+
7+
enum Status {
8+
PROCESSING,
9+
PASS,
10+
INVALID_FORMAT,
11+
}
12+
13+
const status = ref(Status.PROCESSING);
14+
const codeBlockHtml = ref("")
15+
const errorLines = ref(new Set<number>())
16+
let highlighter: HighlighterGeneric<any, any>;
17+
18+
const handleDrop = async (event: DragEvent) => {
19+
status.value = Status.PROCESSING;
20+
const file = event.dataTransfer.files[0];
21+
errorLines.value.clear();
22+
if (!file || !file.name.endsWith(".yml")) {
23+
status.value = Status.INVALID_FORMAT;
24+
return;
25+
}
26+
const ymlContent = await file.text();
27+
try {
28+
parse(ymlContent);
29+
} catch (_) {
30+
status.value = Status.INVALID_FORMAT;
31+
}
32+
ymlContent.split('\n').forEach((line, index) => {
33+
if (line.includes('\t')) {
34+
errorLines.value.add(index + 1);
35+
return;
36+
}
37+
if (!line.includes(":")) {
38+
return;
39+
}
40+
const trimStartLine = line.trimStart();
41+
if (trimStartLine.startsWith("#")) {
42+
return;
43+
}
44+
const spiltLine = trimStartLine.split(":");
45+
if (spiltLine.length !== 2) {
46+
errorLines.value.add(index + 1);
47+
return;
48+
}
49+
const trimEndLine = spiltLine[1].trimEnd();
50+
if (trimEndLine !== "" && !trimEndLine.startsWith(" ")) {
51+
errorLines.value.add(index + 1);
52+
}
53+
});
54+
if (errorLines.value.size !== 0) {
55+
status.value = Status.INVALID_FORMAT;
56+
}
57+
let html = highlighter.codeToHtml(ymlContent, {
58+
lang: 'yaml',
59+
themes: {
60+
light: 'github-light',
61+
dark: 'github-dark'
62+
},
63+
defaultColor: false
64+
}).replace("shiki shiki-themes", "shiki shiki-themes vp-code");
65+
if (status.value === Status.INVALID_FORMAT) {
66+
let count = 0;
67+
const regex = new RegExp("<span class=\"line\">", 'g');
68+
codeBlockHtml.value = html.replace(regex, (match) => {
69+
count++;
70+
if (errorLines.value.has(count)) {
71+
return "<span class=\"line highlighted error\">";
72+
}
73+
return match;
74+
});
75+
} else {
76+
codeBlockHtml.value = html;
77+
status.value = Status.PASS
78+
}
79+
}
80+
81+
onMounted(async () => {
82+
highlighter = await createHighlighter({
83+
themes: ['github-dark', "github-light"],
84+
langs: ['yaml'],
85+
})
86+
})
87+
88+
onUnmounted(() => {
89+
errorLines.value.clear();
90+
highlighter.dispose();
91+
})
92+
</script>
93+
94+
<template>
95+
<div class="drop_zone_parent">
96+
<div class="drop_zone" @drop.prevent="handleDrop" @dragover.prevent>
97+
<span><b class="drop_zone_text" id="plugin_upload_text">Drag and drop a <code>config.yml</code> file here</b></span>
98+
</div>
99+
<div class="important custom-block" v-if="status === Status.INVALID_FORMAT">
100+
<div class="custom-block-title">Unfortunately, there is {{errorLines.size}} errors in your <code>config.yml</code></div>
101+
<div class="language-yaml vp-adaptive-theme">
102+
<span class="lang">yaml</span>
103+
<div v-html="codeBlockHtml"></div>
104+
</div>
105+
</div>
106+
<div class="important custom-block" v-if="status === Status.PASS">
107+
<div class="custom-block-title">Congratulations! There isn't any error in your <code>config.yml</code>!</div>
108+
</div>
109+
</div>
110+
</template>
111+
112+
<style scoped>
113+
.drop_zone_parent {
114+
padding: 20px;
115+
}
116+
117+
.drop_zone {
118+
border-radius: 20px;
119+
padding: 20px;
120+
border-style: dashed;
121+
height: 50px;
122+
display: flex;
123+
justify-content: center;
124+
align-items: center;
125+
}
126+
127+
.drop_zone_text {
128+
margin-left: 20px;
129+
margin-top: 20px;
130+
}
131+
</style>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<script setup lang="ts">
2+
import {ref} from 'vue';
3+
import JSZip from 'jszip';
4+
import {parse} from "yaml";
5+
6+
enum Status {
7+
PROCESSING,
8+
SUCCESS,
9+
INVALID_PLUGIN_JAR,
10+
INVALID_PLUGIN_YML,
11+
IS_PAPER_PLUGIN
12+
}
13+
14+
class PluginInfo {
15+
name: string;
16+
fileName: string;
17+
commands: Set<CommandInfo> = new Set<CommandInfo>();
18+
}
19+
20+
class CommandInfo {
21+
name: string;
22+
description: string;
23+
}
24+
25+
const status = ref(Status.PROCESSING);
26+
const pluginInfo = ref(new PluginInfo());
27+
28+
const handleDrop = async (event: DragEvent) => {
29+
status.value = Status.PROCESSING;
30+
const file = event.dataTransfer.files[0];
31+
pluginInfo.value.fileName = (!file) ? "unknown" : file.name;
32+
if (!file || !file.name.endsWith(".jar")) {
33+
status.value = Status.INVALID_PLUGIN_JAR;
34+
return;
35+
}
36+
try {
37+
const zip = await new JSZip().loadAsync(file);
38+
try {
39+
const ymlContent = await zip.file("plugin.yml").async("string");
40+
try {
41+
const ymlData = parse(ymlContent);
42+
if (ymlData.name === undefined) {
43+
status.value = Status.INVALID_PLUGIN_YML;
44+
return;
45+
}
46+
pluginInfo.value.commands.clear();
47+
pluginInfo.value.name = ymlData.name;
48+
if (ymlData.commands !== undefined) {
49+
for (let name of Object.keys(ymlData.commands)) {
50+
const info = new CommandInfo();
51+
info.name = name;
52+
if(ymlData.commands[name] === null || !("description" in ymlData.commands[name])) {
53+
info.description = undefined;
54+
} else {
55+
info.description = ymlData.commands[name].description;
56+
}
57+
pluginInfo.value.commands.add(info);
58+
}
59+
}
60+
status.value = Status.SUCCESS;
61+
} catch (_) {
62+
status.value = Status.INVALID_PLUGIN_YML;
63+
}
64+
} catch (_) {
65+
try {
66+
const ymlContent = await zip.file("paper-plugin.yml").async("string");
67+
const ymlData = parse(ymlContent);
68+
if (ymlData.name === undefined) {
69+
status.value = Status.INVALID_PLUGIN_YML;
70+
return;
71+
}
72+
pluginInfo.value.name = ymlData.name;
73+
status.value = Status.IS_PAPER_PLUGIN;
74+
return;
75+
} catch (_) {
76+
status.value = Status.INVALID_PLUGIN_YML;
77+
return;
78+
}
79+
}
80+
} catch (_) {
81+
status.value = Status.INVALID_PLUGIN_JAR;
82+
}
83+
};
84+
</script>
85+
86+
<template>
87+
<div class="drop_zone_parent">
88+
<div class="drop_zone" @drop.prevent="handleDrop" @dragover.prevent>
89+
<span><b class="drop_zone_text" id="plugin_upload_text">Drag and drop a plugin.jar file here</b></span>
90+
</div>
91+
<div class="important custom-block" v-if="status === Status.SUCCESS">
92+
<div class="custom-block-title">Successfully parsed plugin <code>{{pluginInfo.name}}</code>,
93+
{{ pluginInfo.commands.size === 0 ? " no commands found." : ` found ${pluginInfo.commands.size} commands inside.` }}
94+
</div>
95+
<ul v-if="pluginInfo.commands.size !== 0">
96+
<li v-for="command in Array.from(pluginInfo.commands)" :key="command.name">
97+
<code>{{ command.name }}</code> - {{ command.description }}
98+
</li>
99+
</ul>
100+
</div>
101+
<div class="danger custom-block" v-if="status === Status.INVALID_PLUGIN_JAR || status === Status.INVALID_PLUGIN_YML">
102+
<div class="custom-block-title">Failed to parse <code>{{pluginInfo.fileName}}</code>,
103+
{{ status === Status.INVALID_PLUGIN_JAR ?
104+
" it is not a valid plugin JAR file." :
105+
" it contains an invalid plugin.yml file." }}
106+
</div>
107+
</div>
108+
<div class="warning custom-block" v-if="status === Status.IS_PAPER_PLUGIN">
109+
<div class="custom-block-title"><code>{{pluginInfo.fileName}}</code>
110+
is a Paper-only plugin, which can‘t register commands in the <code>paper-plugin.yml</code> file.
111+
</div>
112+
</div>
113+
</div>
114+
</template>
115+
116+
<style scoped>
117+
.drop_zone_parent {
118+
padding: 20px;
119+
}
120+
121+
.drop_zone {
122+
border-radius: 20px;
123+
padding: 20px;
124+
border-style: dashed;
125+
height: 50px;
126+
display: flex;
127+
justify-content: center;
128+
align-items: center;
129+
}
130+
131+
.drop_zone_text {
132+
margin-left: 20px;
133+
margin-top: 20px;
134+
}
135+
</style>

docs/en/create-commands/arguments/suggestions/async-suggestions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ The easiest way to create a `CompleteableFuture` for asynchronous suggestions is
3434

3535
```java
3636
new String[] { "dirt", "grass", "cobblestone", }; // [!code --]
37+
3738
CompletableFuture.supplyAsync(() -> { // [!code ++]
3839
return new String[] { "dirt", "grass", "cobblestone", }; // [!code ++]
3940
}); // [!code ++]

docs/en/user-setup/command-conversion/conversion.md

Lines changed: 9 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
order: 1
33
authors:
44
- JorelAli
5+
- MC-XiaoHei
56
---
67

8+
<script setup>
9+
import PluginExtractor from '../../../.vitepress/theme/components/PluginExtractor.vue';
10+
import ConfigValidator from '../../../.vitepress/theme/components/ConfigValidator.vue';
11+
</script>
12+
713
# Command Conversion for server admins
814

915
The CommandAPI can convert plugin commands into "Vanilla compatible" commands automatically on startup. This allows you to use `/execute` and Minecraft functions/tags for plugins that don’t use the CommandAPI. For example, if you want to use the `/hat` command from the plugin `Essentials` in an `/execute` command or from a command block, you can use the CommandAPI's command conversion setting to do so.
@@ -26,68 +32,18 @@ The CommandAPI has 3 different conversion methods, each one more specific and po
2632

2733
Drag a plugin here to view a list of available commands which can be registered for the CommandAPI.
2834

29-
<style>
30-
.drop_zone_parent {
31-
padding: 20px;
32-
}
33-
34-
.drop_zone {
35-
border: 2px solid;
36-
border-radius: 20px;
37-
padding: 20px;
38-
border-style: dashed;
39-
height: 50px;
40-
display: flex;
41-
justify-content: center;
42-
align-items: center;
43-
}
44-
45-
#drop_zone_output {
46-
margin-top: 20px;
47-
}
48-
49-
.drop_zone_text {
50-
margin-left: 20px;
51-
margin-top: 20px;
52-
}
53-
</style>
54-
55-
<div class="drop_zone_parent">
56-
<div class="drop_zone" ondrop="pluginDropHandler(event);" ondragover="pluginDragHandler(event);">
57-
<!-- From https://tablericons.com/. Governed by the MIT license. -->
58-
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-upload" width="32" height="32" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
59-
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
60-
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
61-
<polyline points="7 9 12 4 17 9" />
62-
<line x1="12" y1="4" x2="12" y2="16" />
63-
</svg><span ><h3 class="drop_zone_text" id="plugin_upload_text" >Drag and drop a plugin .jar file here</h3></span>
64-
</div>
65-
</div>
66-
67-
<div id="plugin_upload_output"></div>
35+
<PluginExtractor></PluginExtractor>
6836

6937
## YAML configuration rules
7038

7139
To configure command conversion, the CommandAPI reads this information from the `config.yml` file. This file has a bit of a weird structure, so to put it simply, these are the following rules:
7240

7341
- **`config.yml` cannot have tab characters** - The `config.yml` file _must_ only consist of spaces!
74-
- Indentation is important and should be _two spaces_
42+
- one space after the colon in key-value pairs is required.
7543

7644
If you're uncertain if your configuration is valid (or you're getting weird errors in the console), you can check if your configuration is valid by dropping your `config.yml` file below:
7745

78-
<div class="drop_zone_parent">
79-
<div class="drop_zone" ondrop="configDropHandler(event);" ondragover="configDragHandler(event);">
80-
<!-- From https://tablericons.com/. Governed by the MIT license. -->
81-
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-upload" width="32" height="32" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
82-
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
83-
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
84-
<polyline points="7 9 12 4 17 9" />
85-
<line x1="12" y1="4" x2="12" y2="16" />
86-
</svg><span ><h3 class="drop_zone_text" id="config_upload_text" >Drag and drop your config.yml here</h3></span>
87-
</div>
88-
</div>
89-
90-
<div id="config_upload_output"></div>
46+
<ConfigValidator></ConfigValidator>
9147

9248
### Converting all plugin commands
9349

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
},
2828
"dependencies": {
2929
"@vue/theme": "^2.3.0",
30+
"jszip": "^3.10.1",
3031
"vitepress-i18n": "^1.3.0",
31-
"vue": "^3.5.13"
32+
"vue": "^3.5.13",
33+
"yaml": "^2.6.1"
3234
}
3335
}

0 commit comments

Comments
 (0)