-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAddonDocsParser.kt
More file actions
334 lines (275 loc) · 12.5 KB
/
AddonDocsParser.kt
File metadata and controls
334 lines (275 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
package com.dfsek.terra.codetool.docs
import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.search.FileTypeIndex
import com.intellij.psi.search.GlobalSearchScope
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import org.jetbrains.yaml.YAMLFileType
import org.jetbrains.yaml.psi.YAMLFile
import java.util.concurrent.ConcurrentHashMap
import kotlinx.serialization.decodeFromString
class AddonDocsParser(private val project: Project) {
private val cache = ConcurrentHashMap<String, AddonDocumentation>()
private val yaml = Yaml(configuration = YamlConfiguration(strictMode = false, allowAnchorsAndAliases = true))
@Serializable
data class AddonDocumentation(
val objects: Map<String, ObjectDefinition> = emptyMap(),
val templates: Map<String, Map<String, TemplateDefinition>> = emptyMap()
)
@Serializable
data class ObjectDefinition(
val type: String, val description: String? = null, val types: Map<String, TypeDefinition>? = null
)
@Serializable
data class TypeDefinition(
val description: String? = null
)
@Serializable
data class TemplateDefinition(
@SerialName("abstract") val isAbstract: Boolean? = null,
@SerialName("extends") val extendsTemplate: String? = null,
val params: Map<String, ParameterDefinition> = emptyMap(),
val description: String? = null
)
@Serializable
data class ParameterDefinition(
val type: String, val default: String? = null, val description: String? = null, val required: Boolean? = null
)
@Serializable
private data class RawAddonFile(
val root: Map<String, RawAddonContent> = emptyMap()
)
@Serializable
private data class RawAddonContent(
val objects: Map<String, JsonElement>? = null, val templates: Map<String, JsonElement>? = null
)
/**
* Parse all addon documentation files in the project
*/
fun parseAllAddonDocs(): Map<String, AddonDocumentation> {
return ReadAction.compute<Map<String, AddonDocumentation>, RuntimeException> {
val result = mutableMapOf<String, AddonDocumentation>()
// Find all YAML files in the project
val yamlFiles = FileTypeIndex.getFiles(
YAMLFileType.YML, GlobalSearchScope.allScope(project)
)
val psiManager = PsiManager.getInstance(project)
yamlFiles.forEach { virtualFile ->
// Check if this is an addon documentation file
if (isAddonDocFile(virtualFile)) {
val psiFile = psiManager.findFile(virtualFile) as? YAMLFile
psiFile?.let { yamlFile ->
try {
val addonName = extractAddonName(virtualFile.name)
val documentation = parseAddonDocumentation(yamlFile)
if (documentation != null) {
result[addonName] = documentation
cache[addonName] = documentation
}
} catch (e: Exception) {
// Log error but continue processing other files
println("Error parsing addon doc file ${virtualFile.name}: ${e.message}")
}
}
}
}
result
}
}
/**
* Parse a single addon documentation YAML file
*/
fun parseAddonDocumentation(yamlFile: YAMLFile): AddonDocumentation? {
return try {
val yamlText = yamlFile.text
val rawData = parseRawYamlData(yamlText)
// Extract the first (and typically only) addon from the file
val addonContent = rawData.values.firstOrNull() ?: return null
val objects = parseObjectsFromJson(addonContent.objects)
val templates = parseTemplatesFromJson(addonContent.templates)
AddonDocumentation(objects, templates)
} catch (e: Exception) {
println("Failed to parse YAML file: ${e.message}")
null
}
}
/**
* Parse raw YAML data into a map structure
*/
private fun parseRawYamlData(yamlText: String): Map<String, RawAddonContent> {
return try {
// Parse as a generic map first to handle dynamic keys
val genericMap = yaml.decodeFromString<Map<String, JsonElement>>(yamlText)
val result = mutableMapOf<String, RawAddonContent>()
genericMap.forEach { (addonName, addonElement) ->
if (addonElement is JsonObject) {
val objects = addonElement["objects"] as? JsonObject
val templates = addonElement["templates"] as? JsonObject
result[addonName] = RawAddonContent(
objects = objects?.let { mapOf("objects" to it) },
templates = templates?.let { mapOf("templates" to it) })
}
}
result
} catch (e: Exception) {
println("Error parsing raw YAML data: ${e.message}")
emptyMap()
}
}
/**
* Parse objects from JsonElement
*/
private fun parseObjectsFromJson(objectsMap: Map<String, JsonElement>?): Map<String, ObjectDefinition> {
if (objectsMap == null) return emptyMap()
val objectsElement = objectsMap["objects"] as? JsonObject ?: return emptyMap()
val result = mutableMapOf<String, ObjectDefinition>()
objectsElement.forEach { (objectName, objectElement) ->
if (objectElement is JsonObject) {
val type = (objectElement["type"] as? JsonPrimitive)?.content ?: "UNKNOWN"
val description = (objectElement["description"] as? JsonPrimitive)?.content
val types = parseTypesFromJson(objectElement["types"] as? JsonObject)
result[objectName] = ObjectDefinition(type, description, types)
}
}
return result
}
/**
* Parse types from JsonObject
*/
private fun parseTypesFromJson(typesObject: JsonObject?): Map<String, TypeDefinition>? {
if (typesObject == null) return null
val result = mutableMapOf<String, TypeDefinition>()
typesObject.forEach { (typeName, typeElement) ->
val description = if (typeElement is JsonObject) {
(typeElement["description"] as? JsonPrimitive)?.content
} else null
result[typeName] = TypeDefinition(description)
}
return result
}
/**
* Parse templates from JsonElement
*/
private fun parseTemplatesFromJson(templatesMap: Map<String, JsonElement>?): Map<String, Map<String, TemplateDefinition>> {
if (templatesMap == null) return emptyMap()
val templatesElement = templatesMap["templates"] as? JsonObject ?: return emptyMap()
val result = mutableMapOf<String, Map<String, TemplateDefinition>>()
templatesElement.forEach { (categoryName, categoryElement) ->
if (categoryElement is JsonObject) {
val categoryTemplates = mutableMapOf<String, TemplateDefinition>()
categoryElement.forEach { (templateName, templateElement) ->
if (templateElement is JsonObject) {
val isAbstract = (templateElement["abstract"] as? JsonPrimitive)?.content?.toBoolean()
val extendsTemplate = (templateElement["extends"] as? JsonPrimitive)?.content
val description = (templateElement["description"] as? JsonPrimitive)?.content
val params = parseParametersFromJson(templateElement["params"] as? JsonObject)
categoryTemplates[templateName] = TemplateDefinition(
isAbstract = isAbstract, extendsTemplate = extendsTemplate, params = params, description = description
)
}
}
result[categoryName] = categoryTemplates
}
}
return result
}
/**
* Parse parameters from JsonObject
*/
private fun parseParametersFromJson(paramsObject: JsonObject?): Map<String, ParameterDefinition> {
if (paramsObject == null) return emptyMap()
val result = mutableMapOf<String, ParameterDefinition>()
paramsObject.forEach { (paramName, paramElement) ->
if (paramElement is JsonObject) {
val type = (paramElement["type"] as? JsonPrimitive)?.content ?: "Unknown"
val default = (paramElement["default"] as? JsonPrimitive)?.content
val description = (paramElement["description"] as? JsonPrimitive)?.content
val required = (paramElement["required"] as? JsonPrimitive)?.content?.toBoolean()
result[paramName] = ParameterDefinition(type, default, description, required)
}
}
return result
}
/**
* Check if a file is an addon documentation file
*/
private fun isAddonDocFile(virtualFile: VirtualFile): Boolean {
val name = virtualFile.name
return name.endsWith(".yml") || name.endsWith(".yaml")
}
/**
* Extract addon name from filename
*/
private fun extractAddonName(filename: String): String {
return filename.substringBeforeLast('.').replace('-', '_')
}
/**
* Get documentation for a specific addon
*/
fun getAddonDocumentation(addonName: String): AddonDocumentation? {
return cache[addonName] ?: run {
// Try to reload if not in cache
parseAllAddonDocs()
cache[addonName]
}
}
/**
* Get all available object types across all addons
*/
fun getAllObjectTypes(): Set<String> {
val allDocs = if (cache.isEmpty()) parseAllAddonDocs() else cache
return allDocs.values.flatMap { it.objects.keys }.toSet()
}
/**
* Get all available template types for a category
*/
fun getTemplateTypes(category: String): Set<String> {
val allDocs = if (cache.isEmpty()) parseAllAddonDocs() else cache
return allDocs.values.flatMap { it.templates[category]?.keys ?: emptySet() }.toSet()
}
/**
* Find object definition by name across all addons
*/
fun findObjectDefinition(objectName: String): ObjectDefinition? {
val allDocs = if (cache.isEmpty()) parseAllAddonDocs() else cache
return allDocs.values.firstNotNullOfOrNull { it.objects[objectName] }
}
/**
* Find template definition by category and name
*/
fun findTemplateDefinition(category: String, templateName: String): TemplateDefinition? {
val allDocs = if (cache.isEmpty()) parseAllAddonDocs() else cache
return allDocs.values.firstNotNullOfOrNull { it.templates[category]?.get(templateName) }
}
/**
* Get parameter suggestions for a template
*/
fun getParameterSuggestions(category: String, templateName: String): Map<String, ParameterDefinition> {
val template = findTemplateDefinition(category, templateName) ?: return emptyMap()
val result = template.params.toMutableMap()
// If template extends another, merge parent parameters
template.extendsTemplate?.let { parentName ->
val parentTemplate = findTemplateDefinition(category, parentName)
parentTemplate?.params?.forEach { (key, value) ->
if (!result.containsKey(key)) {
result[key] = value
}
}
}
return result
}
/**
* Clear the cache (useful for testing or when files change)
*/
fun clearCache() {
cache.clear()
}
}