verteilen-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/bun.lock +730 -0
- package/index.ts +1 -0
- package/jest.config.js +8 -0
- package/package.json +42 -0
- package/src/client/analysis.ts +377 -0
- package/src/client/client.ts +230 -0
- package/src/client/cluster.ts +125 -0
- package/src/client/execute.ts +210 -0
- package/src/client/http.ts +45 -0
- package/src/client/javascript.ts +535 -0
- package/src/client/job_execute.ts +216 -0
- package/src/client/job_parameter.ts +41 -0
- package/src/client/os.ts +210 -0
- package/src/client/parameter.ts +58 -0
- package/src/client/resource.ts +121 -0
- package/src/client/shell.ts +147 -0
- package/src/interface/base.ts +82 -0
- package/src/interface/bus.ts +144 -0
- package/src/interface/enum.ts +181 -0
- package/src/interface/execute.ts +47 -0
- package/src/interface/record.ts +131 -0
- package/src/interface/server.ts +91 -0
- package/src/interface/struct.ts +292 -0
- package/src/interface/table.ts +34 -0
- package/src/interface/ui.ts +35 -0
- package/src/interface.ts +50 -0
- package/src/lan/en.json +395 -0
- package/src/lan/zh_TW.json +395 -0
- package/src/plugins/i18n.ts +20 -0
- package/src/script/console_manager.ts +135 -0
- package/src/script/console_server_manager.ts +46 -0
- package/src/script/execute/base.ts +309 -0
- package/src/script/execute/feedback.ts +212 -0
- package/src/script/execute/region_job.ts +14 -0
- package/src/script/execute/region_project.ts +23 -0
- package/src/script/execute/region_subtask.ts +14 -0
- package/src/script/execute/region_task.ts +23 -0
- package/src/script/execute/runner.ts +339 -0
- package/src/script/execute/util_parser.ts +175 -0
- package/src/script/execute_manager.ts +348 -0
- package/src/script/socket_manager.ts +329 -0
- package/src/script/webhook_manager.ts +6 -0
- package/src/script/webhook_server_manager.ts +102 -0
- package/src/util/server/console_handle.ts +248 -0
- package/src/util/server/log_handle.ts +194 -0
- package/test/TEST.ts +63 -0
- package/test/client/execute.test.ts +54 -0
- package/test/client/javascript.test.ts +78 -0
- package/test/client/server.test.ts +26 -0
- package/test/client/task.test.ts +136 -0
- package/test/script/parser.test.ts +110 -0
- package/test/script/socket.test.ts +27 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// ========================
|
|
2
|
+
//
|
|
3
|
+
// Share Codebase
|
|
4
|
+
//
|
|
5
|
+
// ========================
|
|
6
|
+
import { v6 as uuidv6 } from 'uuid';
|
|
7
|
+
import { CronJobState, DataType, ExecuteState, Header, Job, Project, Task, WebsocketPack } from "../../interface";
|
|
8
|
+
import { ExecuteManager_Feedback } from "./feedback";
|
|
9
|
+
import { Util_Parser } from './util_parser';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The execute runner
|
|
13
|
+
*/
|
|
14
|
+
export class ExecuteManager_Runner extends ExecuteManager_Feedback {
|
|
15
|
+
/**
|
|
16
|
+
* Execute project
|
|
17
|
+
*/
|
|
18
|
+
protected ExecuteProject = (project:Project) => {
|
|
19
|
+
if(this.current_t == undefined && project.task.length > 0 && this.t_state != ExecuteState.FINISH){
|
|
20
|
+
// When we are just start it, the project run
|
|
21
|
+
this.current_t = project.task[0]
|
|
22
|
+
this.messager_log(`[Execute] Task Start ${this.current_t.uuid}`)
|
|
23
|
+
this.messager_log(`[Execute] Task cron state: ${this.current_t.cronjob}`)
|
|
24
|
+
this.current_job = []
|
|
25
|
+
this.current_cron = []
|
|
26
|
+
} else if (project.task.length == 0){
|
|
27
|
+
this.current_t = undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* In any case, if the task has value, this mean we are in the task stage, so, just ignore everything.\
|
|
32
|
+
* Go for the task stage
|
|
33
|
+
*/
|
|
34
|
+
if(this.current_t != undefined){
|
|
35
|
+
this.ExecuteTask(project, this.current_t)
|
|
36
|
+
}else{
|
|
37
|
+
/**
|
|
38
|
+
* If we are here, task is none by this case. This can only be
|
|
39
|
+
* * A: We are finish all the tasks, And there is no next project, So just mark as finish for entire process
|
|
40
|
+
* * B: We are finish all the tasks, Go to next project
|
|
41
|
+
*/
|
|
42
|
+
const index = this.current_projects.findIndex(x => x.uuid == project.uuid)
|
|
43
|
+
if(index < this.current_projects.length - 1){
|
|
44
|
+
// * Case A: Next project
|
|
45
|
+
this.messager_log(`[Execute] Project Finish ${this.current_p!.uuid}`)
|
|
46
|
+
this.proxy?.executeProjectFinish([this.current_p!, index])
|
|
47
|
+
this.current_p = this.current_projects[index + 1]
|
|
48
|
+
this.proxy?.executeProjectStart([this.current_p!, index + 1])
|
|
49
|
+
this.t_state = ExecuteState.NONE
|
|
50
|
+
}else{
|
|
51
|
+
// * Case B: Finish entire thing
|
|
52
|
+
this.messager_log(`[Execute] Project Finish ${this.current_p!.uuid}`)
|
|
53
|
+
this.proxy?.executeProjectFinish([this.current_p!, index])
|
|
54
|
+
this.current_p = undefined
|
|
55
|
+
this.state = ExecuteState.FINISH
|
|
56
|
+
this.t_state = ExecuteState.NONE
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Execute task
|
|
63
|
+
*/
|
|
64
|
+
private ExecuteTask = (project:Project, task:Task) => {
|
|
65
|
+
/**
|
|
66
|
+
* When it's the first iteration for this task
|
|
67
|
+
*/
|
|
68
|
+
if(this.t_state == ExecuteState.NONE){
|
|
69
|
+
this.t_state = ExecuteState.RUNNING
|
|
70
|
+
this.current_multithread = task.multi ? this.get_task_multi_count(task) : 1
|
|
71
|
+
this.current_task_count = this.get_task_state_count(task)
|
|
72
|
+
}
|
|
73
|
+
let allJobFinish = false
|
|
74
|
+
const hasJob = task.jobs.length > 0
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* If a task has no job... we have to skip it...
|
|
78
|
+
*/
|
|
79
|
+
if(!hasJob){
|
|
80
|
+
// We end it gracefully.
|
|
81
|
+
this.proxy?.executeTaskStart([task, this.current_task_count ])
|
|
82
|
+
this.proxy?.executeTaskFinish(task)
|
|
83
|
+
this.messager_log(`[Execute] Skip ! No job exists ${task.uuid}`)
|
|
84
|
+
this.ExecuteTask_AllFinish(project, task)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if(task.setupjob){
|
|
89
|
+
allJobFinish = this.ExecuteTask_Setup(project, task, this.current_task_count)
|
|
90
|
+
} else if (task.cronjob){
|
|
91
|
+
allJobFinish = this.ExecuteTask_Cronjob(project, task, this.current_task_count)
|
|
92
|
+
} else {
|
|
93
|
+
allJobFinish = this.ExecuteTask_Single(project, task, this.current_task_count)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (allJobFinish){
|
|
97
|
+
this.ExecuteTask_AllFinish(project, task)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* It will spawn amounts of cronjob and send the tasks for assigned node to execute them one by one
|
|
103
|
+
* @param taskCount Should be equal to cronjob result
|
|
104
|
+
* @returns Is finish executing
|
|
105
|
+
*/
|
|
106
|
+
private ExecuteTask_Cronjob(project:Project, task:Task, taskCount:number):boolean {
|
|
107
|
+
let ns:Array<WebsocketPack> = this.get_idle_open()
|
|
108
|
+
let allJobFinish = false
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* if current_cron length is zero\
|
|
112
|
+
* this means the init process has not been run yet
|
|
113
|
+
*/
|
|
114
|
+
if(this.current_cron.length == 0){
|
|
115
|
+
// First time
|
|
116
|
+
this.Init_CronContainer(task, taskCount)
|
|
117
|
+
this.messager_log(`[Execute] TaskCount: ${taskCount}`)
|
|
118
|
+
} else{
|
|
119
|
+
// If disconnect or deleted...
|
|
120
|
+
/**
|
|
121
|
+
* We query all the cron state and get all the processing first and count it\
|
|
122
|
+
* All we want is to filter out the node which is fully load\
|
|
123
|
+
* So we can follow the multithread limit to send the mission
|
|
124
|
+
*/
|
|
125
|
+
const worker = this.current_cron.filter(x => x.uuid != '').map(x => x.uuid)
|
|
126
|
+
const counter:Array<[string, number]> = []
|
|
127
|
+
worker.forEach(uuid => {
|
|
128
|
+
const index = counter.findIndex(x => x[0] == uuid)
|
|
129
|
+
if(index == -1) counter.push([uuid, 1])
|
|
130
|
+
else counter[index][1] += 1
|
|
131
|
+
})
|
|
132
|
+
const fullLoadUUID = counter.filter(x => x[1] >= this.current_multithread).map(x => x[0])
|
|
133
|
+
ns = ns.filter(x => !fullLoadUUID.includes(x.uuid))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if(this.check_all_cron_end()){
|
|
137
|
+
allJobFinish = true
|
|
138
|
+
}else{
|
|
139
|
+
// Assign worker
|
|
140
|
+
// Find the cron which is need to be execute by a node
|
|
141
|
+
const needs = this.current_cron.filter(x => x.uuid == '' && x.work.filter(y => y.state != ExecuteState.FINISH && y.state != ExecuteState.ERROR).length > 0)
|
|
142
|
+
const min = Math.min(needs.length, ns.length)
|
|
143
|
+
for(let i = 0; i < min; i++){
|
|
144
|
+
needs[i].uuid = ns[i].uuid
|
|
145
|
+
}
|
|
146
|
+
const single = this.current_cron.filter(x => x.uuid != '')
|
|
147
|
+
// Execute
|
|
148
|
+
for(var cronwork of single){
|
|
149
|
+
const index = this.current_nodes.findIndex(x => x.uuid == cronwork.uuid)
|
|
150
|
+
if(index != -1){
|
|
151
|
+
this.ExecuteCronTask(project, task, cronwork, this.current_nodes[index])
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return allJobFinish
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* There will be no CronTask be called, it will go straight to the Execute job section
|
|
160
|
+
* @param taskCount Must be 1
|
|
161
|
+
* @returns Is finish executing
|
|
162
|
+
*/
|
|
163
|
+
private ExecuteTask_Single(project:Project, task:Task, taskCount:number):boolean {
|
|
164
|
+
let allJobFinish = false
|
|
165
|
+
let ns:Array<WebsocketPack> = []
|
|
166
|
+
if(this.current_job.length > 0){
|
|
167
|
+
// If disconnect or deleted...
|
|
168
|
+
const last = this.current_nodes.find(x => x.uuid == this.current_job[0].uuid)
|
|
169
|
+
if(last == undefined){
|
|
170
|
+
ns = this.get_idle()
|
|
171
|
+
this.current_job = []
|
|
172
|
+
}else{
|
|
173
|
+
ns = [last]
|
|
174
|
+
if(ns[0].websocket.readyState != 1){
|
|
175
|
+
ns = this.get_idle()
|
|
176
|
+
this.current_job = []
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}else{
|
|
180
|
+
// First time
|
|
181
|
+
this.sync_local_para(this.localPara!)
|
|
182
|
+
ns = this.get_idle()
|
|
183
|
+
if(ns.length > 0) {
|
|
184
|
+
this.proxy?.executeTaskStart([task, taskCount ])
|
|
185
|
+
this.proxy?.executeSubtaskStart([task, 0, ns[0].uuid])
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (ns.length > 0 && ns[0].websocket.readyState == 1 && this.check_socket_state(ns[0]) != ExecuteState.RUNNING)
|
|
190
|
+
{
|
|
191
|
+
if(this.check_single_end()){
|
|
192
|
+
allJobFinish = true
|
|
193
|
+
}else{
|
|
194
|
+
if(this.current_job.length != task.jobs.length){
|
|
195
|
+
const job:Job = JSON.parse(JSON.stringify(task.jobs[this.current_job.length]))
|
|
196
|
+
const runtime = uuidv6()
|
|
197
|
+
this.current_job.push({
|
|
198
|
+
uuid: ns[0].uuid,
|
|
199
|
+
runtime: runtime,
|
|
200
|
+
state: ExecuteState.RUNNING,
|
|
201
|
+
job: job
|
|
202
|
+
})
|
|
203
|
+
job.index = 0
|
|
204
|
+
job.runtime_uuid = runtime
|
|
205
|
+
this.ExecuteJob(project, task, job, ns[0], false)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return allJobFinish
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private ExecuteTask_Setup(project:Project, task:Task, taskCount:number):boolean {
|
|
213
|
+
let ns:Array<WebsocketPack> = this.get_idle_open()
|
|
214
|
+
let allJobFinish = false
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* if current_cron length is zero\
|
|
218
|
+
* this means the init process has not been run yet
|
|
219
|
+
*/
|
|
220
|
+
if(this.current_cron.length == 0){
|
|
221
|
+
// First time
|
|
222
|
+
this.Init_CronContainer(task, taskCount)
|
|
223
|
+
this.messager_log(`[Execute] TaskCount: ${taskCount}`)
|
|
224
|
+
for(let i = 0; i < this.current_cron.length; i++){
|
|
225
|
+
this.current_cron[i].uuid = this.current_nodes[i].uuid
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if(this.check_all_cron_end()){
|
|
230
|
+
allJobFinish = true
|
|
231
|
+
}else{
|
|
232
|
+
const single = this.current_cron.filter(x => x.uuid != '')
|
|
233
|
+
// Execute
|
|
234
|
+
for(var cronwork of single){
|
|
235
|
+
const index = this.current_nodes.findIndex(x => x.uuid == cronwork.uuid)
|
|
236
|
+
if(index != -1){
|
|
237
|
+
this.ExecuteCronTask(project, task, cronwork, this.current_nodes[index])
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return allJobFinish
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private ExecuteTask_AllFinish(project:Project, task:Task){
|
|
245
|
+
this.proxy?.executeTaskFinish(task)
|
|
246
|
+
this.messager_log(`[Execute] Task Finish ${task.uuid}`)
|
|
247
|
+
const index = project.task.findIndex(x => x.uuid == task.uuid)
|
|
248
|
+
if(index == project.task.length - 1){
|
|
249
|
+
// Finish
|
|
250
|
+
this.current_t = undefined
|
|
251
|
+
this.t_state = ExecuteState.FINISH
|
|
252
|
+
}else{
|
|
253
|
+
// Next
|
|
254
|
+
this.current_t = project.task[index + 1]
|
|
255
|
+
this.t_state = ExecuteState.NONE
|
|
256
|
+
}
|
|
257
|
+
this.current_job = []
|
|
258
|
+
this.current_cron = []
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private ExecuteCronTask = (project:Project, task:Task, work:CronJobState, ns:WebsocketPack) => {
|
|
262
|
+
if(ns.current_job.length < this.current_multithread){
|
|
263
|
+
const rindex = work.work.findIndex(x => x.state == ExecuteState.RUNNING)
|
|
264
|
+
if(rindex != -1) return
|
|
265
|
+
const index = work.work.findIndex(x => x.state == ExecuteState.NONE)
|
|
266
|
+
if(index == 0) this.proxy?.executeSubtaskStart([task, work.id, ns.uuid ])
|
|
267
|
+
if(index == -1) return
|
|
268
|
+
work.work[index].state = ExecuteState.RUNNING
|
|
269
|
+
try {
|
|
270
|
+
const job:Job = JSON.parse(JSON.stringify(task.jobs[index]))
|
|
271
|
+
job.index = work.id
|
|
272
|
+
job.runtime_uuid = work.work[index].runtime
|
|
273
|
+
this.ExecuteJob(project, task, job, ns, true)
|
|
274
|
+
}catch(err){
|
|
275
|
+
this.messager_log(`[ExecuteCronTask Error] UUID: ${task.uuid}, Job count: ${task.jobs.length}, index: ${index}`)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private ExecuteJob = (project:Project, task:Task, job:Job, wss:WebsocketPack, iscron:boolean) => {
|
|
281
|
+
const n:number = job.index!
|
|
282
|
+
this.messager_log(`[Execute] Job Start ${n} ${job.uuid} ${wss.uuid}`)
|
|
283
|
+
this.proxy?.executeJobStart([ job, n, wss.uuid ])
|
|
284
|
+
|
|
285
|
+
ExecuteManager_Runner.string_args_transform(task, job, this.messager_log, this.localPara!, n)
|
|
286
|
+
const h:Header = {
|
|
287
|
+
name: 'execute_job',
|
|
288
|
+
channel: this.uuid,
|
|
289
|
+
data: job
|
|
290
|
+
}
|
|
291
|
+
wss.current_job.push(job.runtime_uuid!)
|
|
292
|
+
const stringdata = JSON.stringify(h)
|
|
293
|
+
wss.websocket.send(stringdata)
|
|
294
|
+
this.jobstack = this.jobstack + 1
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Boradcasting all the parameter and library to all the websocket nodes
|
|
299
|
+
* @param p Target project
|
|
300
|
+
*/
|
|
301
|
+
SyncParameter = (p:Project) => {
|
|
302
|
+
// Get the clone para from it
|
|
303
|
+
this.localPara = JSON.parse(JSON.stringify(p.parameter))
|
|
304
|
+
this.messager_log("[Execute] Sync Parameter !")
|
|
305
|
+
this.messager_log("[Execute] Generate local parameter object")
|
|
306
|
+
// Then phrase the expression to value
|
|
307
|
+
for(let i = 0; i < this.localPara!.containers.length; i++){
|
|
308
|
+
if(this.localPara!.containers[i].type == DataType.Expression && this.localPara!.containers[i].meta != undefined){
|
|
309
|
+
const text = `%{${this.localPara!.containers[i].meta}}%`
|
|
310
|
+
const e = new Util_Parser([...Util_Parser.to_keyvalue(this.localPara!)])
|
|
311
|
+
this.localPara!.containers[i].value = e.replacePara(text)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Boradcasting
|
|
315
|
+
this.sync_local_para(this.localPara!)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
protected Init_CronContainer = (task:Task, taskCount:number) => {
|
|
320
|
+
this.sync_local_para(this.localPara!)
|
|
321
|
+
this.current_cron = []
|
|
322
|
+
// Create the cronjob instance here
|
|
323
|
+
for(let i = 0; i < taskCount; i++){
|
|
324
|
+
const d:CronJobState = {
|
|
325
|
+
id: i,
|
|
326
|
+
uuid: "",
|
|
327
|
+
work: task.jobs.map(x => ({
|
|
328
|
+
uuid: x.uuid,
|
|
329
|
+
runtime: '',
|
|
330
|
+
state: ExecuteState.NONE,
|
|
331
|
+
job: x
|
|
332
|
+
}))
|
|
333
|
+
}
|
|
334
|
+
d.work.forEach((x, j) => x.runtime = uuidv6({}, undefined, i * taskCount + j))
|
|
335
|
+
this.current_cron.push(d)
|
|
336
|
+
}
|
|
337
|
+
this.proxy?.executeTaskStart([task, taskCount ])
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ========================
|
|
2
|
+
//
|
|
3
|
+
// Share Codebase
|
|
4
|
+
//
|
|
5
|
+
// ========================
|
|
6
|
+
import { formula, init } from "expressionparser";
|
|
7
|
+
import { DataType, ENV_CHARACTER, IGNORE_CHARACTER, KeyValue, Parameter, ParameterContainer } from "../../interface";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The worker which helps parsing parameter variables into argument\
|
|
11
|
+
* Including expression executing
|
|
12
|
+
*/
|
|
13
|
+
export class Util_Parser {
|
|
14
|
+
|
|
15
|
+
paras:Array<KeyValue> = []
|
|
16
|
+
public get count() : number {
|
|
17
|
+
return this.paras.length
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(_paras:Array<KeyValue>){
|
|
21
|
+
this.paras = _paras
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
clone = () => {
|
|
25
|
+
const b:Array<KeyValue> = JSON.parse(JSON.stringify(this.paras))
|
|
26
|
+
return new Util_Parser(b)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Turn parameter into a list of keyvalue structure\
|
|
31
|
+
* Exclude the expression datatype
|
|
32
|
+
* @param p Target parameter instance
|
|
33
|
+
* @returns The list of keyvalue
|
|
34
|
+
*/
|
|
35
|
+
static to_keyvalue = (p:Parameter):Array<KeyValue> => {
|
|
36
|
+
return [
|
|
37
|
+
...this._to_keyvalue(p.containers)
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Input a object data, and deep search all of subobject\
|
|
43
|
+
* Phrasing it into keyvalue data
|
|
44
|
+
* @param obj Object
|
|
45
|
+
* @returns Array of keyvalue data
|
|
46
|
+
*/
|
|
47
|
+
private static getDeepKeys = (obj:any, name?:string):Array<[string, any]> => {
|
|
48
|
+
let keys:Array<[string, any]> = []
|
|
49
|
+
for(var key in obj) {
|
|
50
|
+
keys.push([name ? name + "." + key : key, obj[key]]);
|
|
51
|
+
if(typeof obj[key] === "object") {
|
|
52
|
+
if(Array.isArray(obj[key])) {
|
|
53
|
+
if(typeof obj[key]['length'] === 'number'){
|
|
54
|
+
keys.push([name ? name + "." + key + ".length" : key + ".length", obj[key]['length']]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var subkeys = this.getDeepKeys(obj[key]);
|
|
58
|
+
keys = keys.concat(subkeys.map(function(subkey) {
|
|
59
|
+
return [name ? name + "." + key + "." + subkey[0] : key + "." + subkey[0], subkey[1]];
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return keys
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parameter containers into keyvalue list
|
|
68
|
+
*/
|
|
69
|
+
static _to_keyvalue = (p:Array<ParameterContainer>):Array<KeyValue> => {
|
|
70
|
+
const r:Array<KeyValue> = []
|
|
71
|
+
r.push(...p.filter(x => x.type == DataType.Boolean || x.type == DataType.String || x.type == DataType.Textarea || x.type == DataType.Number || x.type == DataType.Expression).map(x => { return { key: x.name, value: x.value.toString() } }))
|
|
72
|
+
const objs = p.filter(x => x.type == DataType.Object)
|
|
73
|
+
const lists = p.filter(x => x.type == DataType.List)
|
|
74
|
+
const selects = p.filter(x => x.type == DataType.Select)
|
|
75
|
+
for(const obj of objs){
|
|
76
|
+
const v = obj.value
|
|
77
|
+
const keys = this.getDeepKeys(v, obj.name)
|
|
78
|
+
r.push(...keys.map(x => { return { key: x[0], value: x[1].toString() } }))
|
|
79
|
+
}
|
|
80
|
+
for(const list of lists){
|
|
81
|
+
const a:Array<any> = list.value
|
|
82
|
+
r.push(...a.map((x, index) => { return { key: list.name + "." + String(index), value: x } }))
|
|
83
|
+
r.push({ key: list.name + ".length", value: a.length })
|
|
84
|
+
}
|
|
85
|
+
for(const select of selects){
|
|
86
|
+
const a:Array<any> = select.meta
|
|
87
|
+
const target = a[select.value]
|
|
88
|
+
r.push({ key: select.name, value: target })
|
|
89
|
+
}
|
|
90
|
+
return r
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Search all the string result and replace to target string\
|
|
95
|
+
* @example
|
|
96
|
+
* replaceAll("ABCBCAB", "AB", "KK") // Result: KKCBCKK
|
|
97
|
+
* @param str string data
|
|
98
|
+
* @param fi feature
|
|
99
|
+
* @param tar replace target
|
|
100
|
+
*/
|
|
101
|
+
static replaceAll = (str:string, fi:string, tar:string):string => {
|
|
102
|
+
let p = str
|
|
103
|
+
while(p.includes(fi)) p = p.replace(fi, tar)
|
|
104
|
+
return p
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Replace a string to environment string\
|
|
109
|
+
* * Include Expression calculation
|
|
110
|
+
* * Include Env string, boolean, number replacing
|
|
111
|
+
* @param text Input text
|
|
112
|
+
* @param paras The keyvalue list
|
|
113
|
+
* @returns The result string
|
|
114
|
+
*/
|
|
115
|
+
replacePara = (text:string):string => {
|
|
116
|
+
let buffer = ''
|
|
117
|
+
let store = ''
|
|
118
|
+
let state:boolean = false
|
|
119
|
+
let ignore:number = -1
|
|
120
|
+
let useExp = false
|
|
121
|
+
for(const v of text){
|
|
122
|
+
if(v == IGNORE_CHARACTER && ignore == -1) ignore = 0
|
|
123
|
+
else if(ignore == 0) ignore = 1
|
|
124
|
+
else if(ignore == 1) ignore = 2
|
|
125
|
+
else if(ignore == 2) ignore = -1
|
|
126
|
+
if(v == ENV_CHARACTER && ignore == -1){
|
|
127
|
+
state = !state
|
|
128
|
+
if(!state) { // End
|
|
129
|
+
if(useExp){
|
|
130
|
+
buffer += this.parse(store)
|
|
131
|
+
}else{
|
|
132
|
+
buffer += this._replacePara(store)
|
|
133
|
+
}
|
|
134
|
+
store = ""
|
|
135
|
+
useExp = false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if(v == '{' && state && store.length == 0) useExp = true
|
|
139
|
+
if(state && v != ENV_CHARACTER && (ignore != 0)) store += v
|
|
140
|
+
if(!state && v != ENV_CHARACTER && (ignore != 0)) buffer += (ignore > 0 ? (ENV_CHARACTER + v) : v)
|
|
141
|
+
}
|
|
142
|
+
return buffer
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Expression magic
|
|
147
|
+
* @param str Input string, the expression part of string only, not the entire sentence
|
|
148
|
+
* @param paras Keyvalue list
|
|
149
|
+
* @returns Result calculation
|
|
150
|
+
*/
|
|
151
|
+
parse = (str:string):string => {
|
|
152
|
+
str = str.substring(1, str.length - 1)
|
|
153
|
+
const parser = init(formula, (term: string) => {
|
|
154
|
+
if(term.includes("_ck_")){
|
|
155
|
+
const index = this.paras.findIndex(x => x.key == "ck")
|
|
156
|
+
if(index != -1) term = Util_Parser.replaceAll(term, "_ck_", this.paras[index].value)
|
|
157
|
+
}
|
|
158
|
+
const index = this.paras.findIndex(x => x.key == term)
|
|
159
|
+
if(index != -1) {
|
|
160
|
+
const n = Number(this.paras[index].value)
|
|
161
|
+
if(Number.isNaN(n)) return this.paras[index].value
|
|
162
|
+
return n
|
|
163
|
+
}
|
|
164
|
+
else return 0
|
|
165
|
+
});
|
|
166
|
+
const r = parser.expressionToValue(str).toString()
|
|
167
|
+
return r
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private _replacePara = (store:string) => {
|
|
171
|
+
const index = this.paras.findIndex(x => x.key == store)
|
|
172
|
+
if(index == -1) return `%${store}%`
|
|
173
|
+
return this.paras[index].value
|
|
174
|
+
}
|
|
175
|
+
}
|