-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathskconfig.sk
341 lines (290 loc) · 14.6 KB
/
skconfig.sk
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
335
336
337
338
339
340
341
# This script facilitates access to Skript's built-in configuration format,
# by providing special syntaxes to interact with configuration files.
#
# Any file respecting Skript's default config structure can be parsed and its
# contents mapped to a list variable. All previous, non-conflicting elements
# of the variable are not cleared.
# A file violating the config structure will fail to be mapped and, by default,
# output will be sent to the console.
#
# A list variable can also be written/mapped to a file. This is done by rebuilding
# the configuration structure based on the variable's tree.
#
# Mapping is, by default, done asynchronously through all sub-nodes in a recursive manner.
#
# When loading, values of entries go through the registered value handlers of the current script.
# A value that cannot be parsed fails to be added to the resulting variable and an error is thrown.
#
# Similarly, when writing, values go through the registered value handlers of the current script
# which will convert the value to a string which can be parsed when loading. If the value cannot be
# converted, it will not be added to the resulting configuration and an error is thrown.
#
# User-defined value handlers (auxiliary value handlers) are stored in a special list variable
# defined below and are different for each individual script.
import:
java.lang.reflect.Array
java.io.File
java.io.PrintWriter
java.util.TreeMap
ch.njol.skript.Skript
ch.njol.skript.config.Config
ch.njol.skript.config.EntryNode
ch.njol.skript.config.SectionNode
ch.njol.skript.lang.function.Function
ch.njol.skript.lang.ParseContext
ch.njol.skript.lang.Variable
ch.njol.skript.lang.parser.ParserInstance
ch.njol.skript.log.BlockingLogHandler
ch.njol.skript.log.SkriptLogger
ch.njol.skript.registrations.Classes
ch.njol.skript.variables.Variables
com.btk5h.skriptmirror.FunctionWrapper
com.btk5h.skriptmirror.skript.reflect.sections.Section
options:
# A config's file extension.
# It is generally not recommended to modify this due to the risk of
# breaking compatibility with other scripts/configurations.
file-extension: ".skconfig"
# The key-value separator for entry nodes.
# It is generally not recommended to modify this due to the risk of
# breaking compatibility with other scripts/configurations.
# A colon is used by default for parity with Skript's standard format.
node-separator: ":"
# Whether or not to send loading output (errors, warnings, etc.).
# This is sent to the console recipient at runtime.
show-loading-output: true
# The variable in which to store auxiliary value handlers.
handler-storage: -.skconfig::handlers
# Attempts to parse the given input string using the given value handlers.
# This first goes through the default handlers, then through the provided ones.
local expression (parsed|deseriali(s|z)ed) config value %string% [(with|using) [value] handlers %-objects%]:
return type: object
get:
# Default parsers
loop "java.lang.String", "java.lang.Integer", "java.lang.Double", "java.lang.Boolean":
set {_result} to Classes.parseSimple(expr-1, (class loop-value), ParseContext.SCRIPT)
continue if {_result} is not set
if loop-value is "java.lang.String":
set {_result} to {_result}.replaceAll("(?<!\\)%%nl%%", newline)
return {_result}
# Auxiliary parsers
loop exprs-2:
set {_handler} to loop-value
if {_handler} is an instance of Function:
# Multidimensional array workaround
set {_args} to Array.newInstance(Object.class, 2, 1)
Array.set({_args}, 0, [expr-1])
Array.set({_args}, 1, [true])
set {_result} to {_handler}.execute({_args})[0]
{_handler}.resetReturnValue()
else:
clear {_result}
run section {_handler} with expr-1, true and store the result in {_result}
return {_result} if {_result} is set
return
# Attempts, using the given value handlers, to convert the given object to
# a string that can be saved to a configuration and later parsed back.
#
# This first goes through the default handlers, then through the provided ones.
local expression seriali(s|z)ed config value %object% [(with|using) [value] handlers %-objects%]:
return type: string
get:
# Default serialisers
set {_value} to first element of expr-1
if {_value} is a text:
replace all newline in {_value} with "%%nl%%"
return """%{_value}%"""
return "%{_value}%" if {_value} is a number or a boolean
# Auxiliary serialisers
loop exprs-2:
set {_handler} to loop-value
if {_handler} is an instance of Function:
# Multidimensional array workaround
set {_args} to Array.newInstance(Object.class, 2, 1)
Array.set({_args}, 0, [expr-1])
Array.set({_args}, 1, [false])
set {_result} to {_handler}.execute({_args})[0]
{_handler}.resetReturnValue()
else:
clear {_result}
run section {_handler} with expr-1, false and store the result in {_result}
return {_result} if {_result} is set
return
# Returns the available value handlers of a given script.
plural expression [config[uration]] [value] handlers (from|of|for) script[s] %strings%:
return type: objects
parse:
set {_skript-data-folder} to Skript.getInstance().getDataFolder()
set {_scripts-folder} to Skript.SCRIPTSFOLDER
continue
get:
loop exprs-1:
set {_script} to loop-value
if {_script} does not end with ".sk":
set {_script} to join {_script} and ".sk"
set {_script} to join {_scripts-folder}, File.separator, {_script}
set {_file} to new File({_skript-data-folder}, {_script})
return if {_file}.exists() is false
# Madness a la Skript
set {_path} to {_skript-data-folder}.toPath().toAbsolutePath().resolve({_scripts-folder}).relativize({_file}.toPath().toAbsolutePath()).toString()
loop {{@handler-storage}::%{_path}%::*}:
add 1 to {_index}
set {_handlers::%{_index}%} to loop-value-2
return {_handlers::*}
# Maps the contents of a config to a list variable.
# This fails for inexistent files or files that violate the format.
#
# An optional list of value handlers can be provided to use for parsing.
# If none are provided, the handlers of the current script will be used.
effect load [script] config[uration] %string% in[to] %~objects% [(with|using) [value] handlers %-objects%]:
parse:
expr-2 is instance of Variable
expr-2.isList() is true
set {_script} to ParserInstance.get().getCurrentScript().getFileName()
continue
trigger:
delay the effect
set {_path} to expr-1
if {_path} does not end with {@file-extension}:
set {_path} to join {_path} and {@file-extension}
set {_file} to new File({_path})
continue if {_file}.exists() is false
# Retains all parsing output and prints it only if required to do so.
set {_handler} to SkriptLogger.startRetainingLog()
set {_config} to new Config({_file}, true, true, {@node-separator})
set {_root} to {_config}.getMainNode()
{_root}.convertToEntries(-1)
{_handler}.stop()
{_handler}.printErrors() if {@show-loading-output} is true
continue if {_handler}.hasErrors() is true
set {_variable} to (raw expr-2).getName().getSingle(event)
set {_variable} to first (length of {_variable} - 3) characters of {_variable}
set {_local} to (raw expr-2).isLocal()
# This can probably be swapped for a direct replacement of the variable's tree with the
# config's tree, but doing it in this fashion simplifies the work of adding type converters.
create new section with {_variable}, {_local}, {_event}, {_node}, {_handlers::*}, {_section} stored in {_section-mapper}:
if {_node} is an instance of EntryNode:
set {_value} to parsed config value ({_node}.getValue()) using handlers {_handlers::*}
if {_value} is not set:
if {@show-loading-output} is true:
SkriptLogger.setNode({_node})
Skript.error("Can't understand the value '%{_node}.getValue()%'")
else: # Do not overwrite existing value with null.
Variables.setVariable({_variable}, {_value}, {_event}, {_local})
else if {_node} is an instance of SectionNode:
loop ...{_node}:
set {_key} to join {_variable}, (Variable.SEPARATOR), (loop-value.getKey())
run section {_section} with {_key}, {_local}, {_event}, loop-value, {_handlers::*}, {_section} and wait
if exprs-3 is set:
set {_script-handlers::*} to exprs-3
else:
set {_script-handlers::*} to value handlers of script {_script}
run section {_section-mapper} async with {_variable}, {_local}, event, {_root}, {_script-handlers::*}, {_section-mapper} and wait
continue
# Writes the contents of a list variable to a config file.
# All previous contents of the file are overwritten.
# If the file does not exist, a new one is created.
#
# An optional list of value handlers can be provided to use for serialising.
# If none are provided, the handlers of the current script will be used.
#
# Comments can be added to entry nodes by having a special entry
# in the list whose key is prefixed with '#'.
# Comments can be added to section nodes by having a special entry
# in the list whose key is prefixed with '#>'.
#
# For example, the variable structure:
# ```
# {config::foo} = "hello"
# {config::foo::bar} = "world"
# {config::#foo} = "this is a comment for a key-value node"
# {config::#>foo} = "this is a comment for a section node"
# ```
#
# produces the configuration:
# ```
# foo: # this is a comment for a section node
# bar: "world"
# foo: "hello" # this is a comment for a key-value node
# ```
effect (write|map) [script] config[uration] %~objects% (to|in[to]) [file] %string% [(with|using) [value] handlers %-objects%]:
parse:
expr-1 is instance of Variable
expr-1.isList() is true
set {_script} to ParserInstance.get().getCurrentScript().getFileName()
continue
trigger:
delay the effect
set {_path} to expr-2
if {_path} does not end with {@file-extension}:
set {_path} to join {_path} and {@file-extension}
set {_file} to new File({_path})
# Clear existing contents.
if {_file}.createNewFile() is false:
new PrintWriter({_file}).close()
# Block empty config warning.
set {_handler} to SkriptLogger.startLogHandler(new BlockingLogHandler())
set {_config} to new Config({_file}, true, true, {@node-separator})
set {_root} to {_config}.getMainNode()
{_handler}.stop()
set {_variable} to (raw expr-1).getRaw(event)
create new section with {_tree}, {_old-tree}, {_parent}, {_handlers::*}, {_section} stored in {_section-mapper}:
loop ...{_tree}.entrySet():
set {_key} to loop-value.getKey()
continue loop if {_key} starts with "##"
set {_value} to loop-value.getValue()
set {_is-tree} to true if {_value} is an instance of TreeMap, else false
set {_comment-variable-prefix} to "##>" if {_is-tree} is true, else "##"
if {_key} is not set:
set {_key} to {_parent}.getKey()
set {_actual-parent} to {_parent}.getParent()
set {_comment} to {_old-tree}.get(join {_comment-variable-prefix}, {_key})
else:
set {_actual-parent} to {_parent}
set {_comment} to {_tree}.get(join {_comment-variable-prefix}, {_key})
if {_comment} does not exist:
set {_comment} to ""
else:
set {_comment} to join " ## " and {_comment}
if {_is-tree} is true:
set {_child} to new SectionNode({_key}, {_comment}, {_parent}, 1)
{_parent}.add({_child})
run section {_section} with {_value}, {_tree}, {_child}, {_handlers::*}, {_section} and wait
else:
set {_serialised-value} to serialised config value {_value} using handlers {_handlers::*}
if {_serialised-value} is not set:
if {@show-loading-output} is true:
SkriptLogger.setNode(null)
Skript.error("Can't save '%{_value}%' in a config because there is no serialiser for it")
else:
set {_node} to new EntryNode({_key}, {_serialised-value}, {_comment}, {_actual-parent}, -1)
{_actual-parent}.add({_node})
if exprs-3 is set:
set {_script-handlers::*} to exprs-3
else:
set {_script-handlers::*} to value handlers of script {_script}
run section {_section-mapper} async with {_variable}, null, {_root}, {_script-handlers::*}, {_section-mapper} and wait
{_config}.save({_file})
continue
# Registers a user-defined value handler with the given string identifier.
# This accepts dyadic sections or references to dyadic functions.
#
# The value handler is typically local to the script, but may be accessed by
# other scripts using special syntax.
#
# The first parameter is the input and the second is a boolean indicating whether the input is
# to undergo a parsing or deserialisation. A value of 'true' indicates parsing, while 'false'
# indicates deserialisation.
#
# For plain handlers of class infos, it is recommended to use the class info's code name
# as the string identifier of the auxiliary value handler.
effect register [a] [new] [script] config[uration] %string% [value] handler (with|using) %object%:
parse:
set {_script} to ParserInstance.get().getCurrentScript().getFileName()
continue
trigger:
stop if expr-2 is not instance of Section, Function or FunctionWrapper
set {_handler} to expr-2
if {_handler} is an instance of FunctionWrapper:
set {_handler} to {_handler}.getFunction() # unwrap
set {{@handler-storage}::%{_script}%::%expr-1%} to {_handler}