forked from ZeraGmbH/Blockly.Net
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBlocklyExtensions.cs
More file actions
287 lines (238 loc) · 12.2 KB
/
BlocklyExtensions.cs
File metadata and controls
287 lines (238 loc) · 12.2 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
using BlocklyNet.Core;
using BlocklyNet.Core.Blocks;
using BlocklyNet.Core.Model;
using BlocklyNet.Extensions.Models;
using BlocklyNet.Scripting.Engine;
using System.Reflection;
using System.Text.Json.Nodes;
namespace BlocklyNet.Extensions.Builder;
/// <summary>
/// Configuration methods for script parsers.
/// </summary>
public static class BlocklyExtensions
{
/// <summary>
/// Tag class to create typed configuration helper.
/// </summary>
static class BlockBuilder
{
/// <summary>
/// Create a txped configuration helper.
/// </summary>
/// <typeparam name="TParser">Type of the parser to use.</typeparam>
/// <param name="parser">Parser to use.</param>
/// <param name="models">Block model manager.</param>
/// <returns>Parser configuration helper instance.</returns>
public static BlockBuilder<TParser> Create<TParser>(Parser<TParser> parser, IScriptModels models) where TParser : Parser<TParser> => new(parser, models);
}
/// <summary>
/// Parser configuration class.
/// </summary>
/// <typeparam name="TParser">Type of the parser to use.</typeparam>
/// <param name="parser">Parser to use.</param>
/// <param name="models">Block model manager.</param>
public class BlockBuilder<TParser>(Parser<TParser> parser, IScriptModels models) where TParser : Parser<TParser>
{
/// <summary>
/// All models defined so far.
/// </summary>
private readonly ModelCache _models = new();
/// <summary>
/// The parser to configure.
/// </summary>
private readonly Parser<TParser> _parser = parser;
/// <summary>
/// Generate all blockly definitions including the toolbox entries.
/// </summary>
public void CreateDefinitions() => _models.CreateDefinitions();
/// <summary>
/// Add a self describing block class to the parser.
/// </summary>
/// <param name="registerAs">Set if this block should be handled like a model.</param>
/// <param name="category">Toolbox category to use.</param>
/// <param name="name">Optional display name of the block.</param>
/// <typeparam name="TBlock">Type of the block.</typeparam>
public void AddBlock<TBlock>(Type? registerAs = null, string? category = null, string? name = null) where TBlock : Block, new()
{
/* Attribute indicator is required. */
var blockAttribute = typeof(TBlock).GetCustomAttributes<CustomBlockAttribute>().Single();
/* Add the block type using the unique block key from the attribute. */
var key = blockAttribute.Key;
_parser.AddBlock<TBlock>(key);
/* Block definition is required as well. */
var blockDefinition = blockAttribute.Definition;
/* Convert to a JSON object, append the block key and remember in parser. */
var blockJson = (JsonObject)JsonNode.Parse(blockDefinition)!;
blockJson["type"] = key;
_parser.BlockDefinitions.Add(blockJson);
/* A toolbox entry is always provided but it can be extended using the attribute, */
var toolbox =
string.IsNullOrEmpty(blockAttribute.Toolbox)
? []
: (JsonObject)JsonNode.Parse(blockAttribute.Toolbox)!;
/* Fill in the core data of the toolbox entry and report it to the parser. */
var message0 = (string)blockJson["message0"]!;
var part0 = message0?.Split(" ").FirstOrDefault(key);
toolbox["_hidden"] = blockAttribute.Hidden;
toolbox["_kind"] = registerAs != null ? "model" : "block";
toolbox["_name"] = part0;
toolbox["kind"] = "block";
toolbox["type"] = key;
_parser.ToolboxEntries.Add(Tuple.Create(blockAttribute.Category, toolbox));
/* Remember the model type - future models can now reference the property type. */
if (registerAs == null) return;
_models.Add(registerAs, key);
/* For value types add a nullable as well. */
if (registerAs.IsValueType) _models.Add(typeof(Nullable<>).MakeGenericType(registerAs), key);
/* Register. */
models.SetModel(registerAs, key, name ?? part0 ?? key, category);
}
/// <summary>
/// Register a model in the parser.
/// </summary>
/// <typeparam name="TModel">Model type.</typeparam>
/// <param name="key">Block key for the model.</param>
/// <param name="name">Display name for the model.</param>
/// <param name="category">Toolbox category to use.</param>
public void AddModel<TModel>(string key, string name, string? category = null) where TModel : class, new()
=> _models.Add<TModel>(key, () =>
{
/* Initialize the generic model generator. */
var (blockDefinition, toolboxEntry) = ModelBlock<TModel>.Initialize(key, name, category, _models, CreateScratchModel);
/* Register the block and toolbox definition in the parser. */
_parser.ModelDefinitions.Add(blockDefinition);
_parser.ToolboxEntries.Add(Tuple.Create(string.IsNullOrEmpty(category) ? "*" : category, toolboxEntry));
/* Add the related block to the parser. */
_parser.AddBlock<ModelBlock<TModel>>(key);
/* Register. */
models.SetModel<TModel>(key, name, category);
});
/// <summary>
/// See if we can create a model on the fly.
/// </summary>
/// <param name="type">Type to create a model for.</param>
/// <param name="key">Blockly key of the potentially created model.</param>
/// <param name="name">Name of the parent model.</param>
/// <param name="category">Toolbox category to use.</param>
/// <returns>Set if a model has been created.</returns>
private bool CreateScratchDictionary(Type type, string key, string name, string? category)
{
/* Key must be a known enumeration. */
var keyType = type.GetGenericArguments()[0];
if (!keyType.IsEnum || !_models.Contains(keyType)) return false;
/* Create the model. */
var addModelMethod = GetType().GetMethod("AddModel")!;
var addModel = addModelMethod.MakeGenericMethod(type);
addModel.Invoke(this, [key, $"{name} {key.Split("_").Last()}", category]);
/* Did it. */
return true;
}
/// <summary>
/// See if we can create a model on the fly.
/// </summary>
/// <param name="type">Type to create a model for.</param>
/// <param name="key">Blockly key of the potentially created model.</param>
/// <param name="name">Name of the parent model.</param>
/// <param name="category">Toolbox category to use.</param>
/// <returns>Set if a model has been created.</returns>
private bool CreateScratchList(Type type, string key, string name, string? category)
{
/* Create the model. */
var addModelMethod = GetType().GetMethod("AddModel")!;
var addModel = addModelMethod.MakeGenericMethod(type);
addModel.Invoke(this, [key, $"{name} {key.Split("_").Last()}", category]);
/* Did it. */
return true;
}
/// <summary>
/// See if we can create a model on the fly.
/// </summary>
/// <param name="type">Type to create a model for.</param>
/// <param name="key">Blockly key of the potentially created model.</param>
/// <param name="name">Name of the parent model.</param>
/// <param name="category">Toolbox category to use.</param>
/// <returns>Set if a model has been created.</returns>
private bool CreateScratchModel(Type type, string key, string name, string? category)
{
if (!type.IsGenericType) return false;
var generic = type.GetGenericTypeDefinition();
/* See if the type is a dictionary. */
if (generic == typeof(Dictionary<,>)) return CreateScratchDictionary(type, key, name, category);
/* See if the type is a list. */
if (generic == typeof(List<>)) return CreateScratchList(type, key, name, category);
return false;
}
/// <summary>
/// Register a enumeration in the parser.
/// </summary>
/// <typeparam name="T">Enumeration type.</typeparam>
/// <param name="key">Block key for the enumeration.</param>
/// <param name="name">Display name for the enumeration.</param>
/// <param name="category">Toolbox category to put the enumeration in.</param>
public void AddEnum<T>(string key, string name, string? category = null) where T : Enum
=> _models.Add<T>(key, () =>
{
/* Initialize the generic model generator. */
var (blockDefinition, toolboxEntry) = EnumBlock<T>.Initialize(key, name);
/* Register the block and toolbox definition in the parser. */
_parser.ModelDefinitions.Add(blockDefinition);
_parser.ToolboxEntries.Add(Tuple.Create(string.IsNullOrEmpty(category) ? "Enum" : category, toolboxEntry));
/* Add the related block to the parser. */
_parser.AddBlock<EnumBlock<T>>(key);
/* Register. */
models.SetEnum<T>(key, name, category);
});
}
/// <summary>
/// Configure a parser with all library and standard script blocks.
/// </summary>
/// <typeparam name="TParser">Type of the parser to use.</typeparam>
/// <param name="parser">Parser to configure.</param>
/// <param name="models">Remember all model blocks.</param>
/// <param name="custom">Method to add custom definitions.</param>
/// <returns>The configured parser.</returns>
public static TParser AddCustomBlocks<TParser>(this TParser parser, IScriptModels models, Action<BlockBuilder<TParser>>? custom) where TParser : Parser<TParser>
{
/* Use our little helper. */
var builder = BlockBuilder.Create(parser, models);
/* Add library extensions: enumerations, any order. */
builder.AddEnum<GroupResultType>("group_execution_result_type", "Result of the Exceution", "ExecutionGroups");
/* Add library extensions: models, leaves first up to root. */
builder.AddModel<GroupResult>("group_execution_result", "Result of a group execution", "ExecutionGroups");
builder.AddModel<GroupStatusCommon>("group_execution_status", "Status of a group execution", "ExecutionGroups");
/* Add library extensions: blocks, any order. */
builder.AddBlock<CreateRunScriptParameter>();
builder.AddBlock<Delay>();
builder.AddBlock<ExecutionGroup>();
builder.AddBlock<ExtractProperty>();
builder.AddBlock<FormatAsString>();
builder.AddBlock<GetGroupStatus>();
builder.AddBlock<GetLastException>();
builder.AddBlock<HttpRequest>();
builder.AddBlock<Now>();
builder.AddBlock<ParseJson>();
builder.AddBlock<ParseNumber>();
builder.AddBlock<ReadFromModel>();
builder.AddBlock<RequestUserInput>();
builder.AddBlock<RunParallel>();
builder.AddBlock<RunScript>();
builder.AddBlock<SetProgress>();
builder.AddBlock<TextContains>();
builder.AddBlock<Throw>();
builder.AddBlock<TryCatchFinally>();
builder.AddBlock<UpdateModelProperty>();
/* Around XML. */
builder.AddModel<XmlAttribute>("xml_attribute", "XML DOM Attribute", "XML");
builder.AddModel<XmlNode>("xml_node", "XML DOM Node", "XML");
builder.AddModel<XmlFile>("xml_file", "XML DOM", "XML");
builder.AddBlock<AddToXmlParent>();
builder.AddBlock<CreateXmlDocument>();
builder.AddBlock<QueryXmlDocument>();
/* Add custom definitions. */
custom?.Invoke(builder);
/* Create definitions. */
builder.CreateDefinitions();
/* And use the standard block definitions. */
return parser.AddStandardBlocks();
}
}