theprogrammablemind 7.1.4-beta.3
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/client.js +1473 -0
- package/index.js +27 -0
- package/lines.js +61 -0
- package/package.json +63 -0
- package/readme +132 -0
- package/src/config.js +2129 -0
- package/src/digraph.js +110 -0
- package/src/flatten.js +143 -0
- package/src/generators.js +282 -0
- package/src/helpers.js +216 -0
- package/src/semantics.js +293 -0
- package/src/unflatten.js +128 -0
package/client.js
ADDED
@@ -0,0 +1,1473 @@
|
|
1
|
+
const { Semantics, Semantic } = require('./src/semantics')
|
2
|
+
const { Generators, Generator } = require('./src/generators')
|
3
|
+
const Digraph = require('./src/digraph')
|
4
|
+
const fetch = require('node-fetch')
|
5
|
+
const base64 = require('base-64')
|
6
|
+
const deepEqual = require('deep-equal')
|
7
|
+
const _ = require('lodash')
|
8
|
+
const stringify = require('json-stable-stringify')
|
9
|
+
const Lines = require('./lines')
|
10
|
+
const flattens = require('./src/flatten')
|
11
|
+
const { appendNoDups, InitCalls } = require('./src/helpers')
|
12
|
+
const sortJson = require('sort-json')
|
13
|
+
const util = require('util')
|
14
|
+
const { diffString } = require('json-diff')
|
15
|
+
|
16
|
+
const isJest = () => {
|
17
|
+
return process.env.JEST_WORKER_ID !== undefined;
|
18
|
+
}
|
19
|
+
|
20
|
+
let fs
|
21
|
+
let ArgumentParser
|
22
|
+
if (typeof window === 'undefined' || isJest()) {
|
23
|
+
fs = require('fs')
|
24
|
+
ArgumentParser = require('argparse').ArgumentParser
|
25
|
+
}
|
26
|
+
|
27
|
+
const ask = (config) => (asks) => {
|
28
|
+
for (let ask of asks) {
|
29
|
+
config.addMotivation({
|
30
|
+
match: (args) => ask.matchr(args),
|
31
|
+
apply: (args) => ask.applyr(args)
|
32
|
+
})
|
33
|
+
}
|
34
|
+
config.addMotivation({
|
35
|
+
match: ({context}) => context.marker == 'controlEnd' || context.marker == 'controlBetween',
|
36
|
+
apply: (args) => {
|
37
|
+
for (let ask of asks) {
|
38
|
+
let matchq = ask.matchq
|
39
|
+
let applyq = ask.applyq
|
40
|
+
if (!matchq) {
|
41
|
+
let wasAsked = false
|
42
|
+
matchq = () => !wasAsked,
|
43
|
+
applyq = (args) => {
|
44
|
+
wasAsked = true
|
45
|
+
applyq(args)
|
46
|
+
}
|
47
|
+
}
|
48
|
+
if (matchq(args)) {
|
49
|
+
args.context.motivationKeep = true
|
50
|
+
args.context.verbatim = applyq(args)
|
51
|
+
args.context.isResponse = true;
|
52
|
+
delete args.context.controlRemove;
|
53
|
+
args.context.controlKeepMotivation = true;
|
54
|
+
break
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
})
|
59
|
+
}
|
60
|
+
|
61
|
+
const vimdiff = (actualJSON, expectedJSON) => {
|
62
|
+
const path = '.'
|
63
|
+
const actual = sortJson(actualJSON, { depth: 25 })
|
64
|
+
fs.writeFileSync(`${path}/actual.json`, JSON.stringify(actual, 0, 2))
|
65
|
+
const expected = sortJson(expectedJSON, { depth: 25 })
|
66
|
+
fs.writeFileSync(`${path}/expected.json`, JSON.stringify(expected, 0, 2))
|
67
|
+
// console.log(`vimdiff ${path}/actual.json ${path}/expected.json`)
|
68
|
+
{
|
69
|
+
const editor = process.env.EDITOR || 'vimdiff'
|
70
|
+
const child = child_process.spawn(editor, [`${path}/expected.json`, `${path}/actual.json`], { stdio: 'inherit' })
|
71
|
+
child.on('exit', function (e, code) {
|
72
|
+
console.log('finished')
|
73
|
+
})
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
const listable = (hierarchy) => (c, type) => {
|
78
|
+
if (hierarchy.isA(c.marker, type)) {
|
79
|
+
return true
|
80
|
+
}
|
81
|
+
if (c.marker === 'list') {
|
82
|
+
for (const t of c.types) {
|
83
|
+
if (hierarchy.isA(t, type)) {
|
84
|
+
return true
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
return false
|
89
|
+
}
|
90
|
+
|
91
|
+
const isA = (hierarchy) => (child, parent) => hierarchy.isA(child, parent)
|
92
|
+
|
93
|
+
const asList = (context) => {
|
94
|
+
if (context.marker === 'list') {
|
95
|
+
return context
|
96
|
+
}
|
97
|
+
return {
|
98
|
+
marker: 'list',
|
99
|
+
types: [context.marker],
|
100
|
+
value: [context]
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
const setupArgs = (args, config, logs, hierarchy) => {
|
105
|
+
config.setArgs(args)
|
106
|
+
args.calls = new InitCalls()
|
107
|
+
args.km = (name) => config.getConfig(name)
|
108
|
+
args.kms = config.getConfigs()
|
109
|
+
args.config = config
|
110
|
+
args.hierarchy = hierarchy
|
111
|
+
args.isA = isA(hierarchy)
|
112
|
+
args.listable = listable(hierarchy)
|
113
|
+
args.asList = asList
|
114
|
+
args.retry = () => { throw new RetryError() }
|
115
|
+
args.ask = ask(config)
|
116
|
+
args.motivation = (m) => config.addMotivation(m)
|
117
|
+
args.s = (c) => config.getSemantics(logs).apply(args, c)
|
118
|
+
args.g = (c) => config.getGenerators(logs).apply(args, c)
|
119
|
+
args.gp = (c) => config.getGenerators(logs).apply(args, { ...c, paraphrase: true, isResponse: false, response: false})
|
120
|
+
args.gr = (c) => config.getGenerators(logs).apply(args, { ...c, paraphrase: false, isResponse: true })
|
121
|
+
args.e = (c) => config.getEvaluator(args.s, logs, c)
|
122
|
+
args.log = (message) => logs.push(message)
|
123
|
+
// config.getAddedArgs(args)
|
124
|
+
args.gs = gs(args.g)
|
125
|
+
args.gsp = gs(args.gp)
|
126
|
+
args.gsr = gs(args.gr)
|
127
|
+
}
|
128
|
+
|
129
|
+
const gs = (g) => (contexts, separator, lastSeparator) => {
|
130
|
+
let s = ''
|
131
|
+
if (!separator) {
|
132
|
+
separator = ' '
|
133
|
+
}
|
134
|
+
if (!lastSeparator) {
|
135
|
+
lastSeparator = separator
|
136
|
+
}
|
137
|
+
let nextSeparator = ''
|
138
|
+
for (let i = 0; i < contexts.length; ++i) {
|
139
|
+
const context = contexts[i]
|
140
|
+
const value = g(context)
|
141
|
+
if (i > 0) {
|
142
|
+
if (i === contexts.length - 1) {
|
143
|
+
nextSeparator = lastSeparator
|
144
|
+
} else {
|
145
|
+
nextSeparator = separator
|
146
|
+
}
|
147
|
+
}
|
148
|
+
s += nextSeparator + value
|
149
|
+
}
|
150
|
+
return s
|
151
|
+
}
|
152
|
+
|
153
|
+
const matching = (actual, expected) => {
|
154
|
+
if (!deepEqual(stringify(sortJson(actual, { depth: 25 })), stringify(sortJson(expected, { depth: 25 })))) {
|
155
|
+
return false
|
156
|
+
}
|
157
|
+
return true
|
158
|
+
}
|
159
|
+
|
160
|
+
const analyzeMetaData = (right, wrong) => {
|
161
|
+
const rOpChoices = right.opChoices
|
162
|
+
const wOpChoices = wrong.opChoices
|
163
|
+
if (!rOpChoices || !wOpChoices) {
|
164
|
+
return []
|
165
|
+
}
|
166
|
+
const n = rOpChoices.length > wOpChoices.length ? wOpChoices.length : rOpChoices.length
|
167
|
+
for (let i = 0; i < n; ++i) {
|
168
|
+
const rOp = rOpChoices[i].op
|
169
|
+
const wOp = wOpChoices[i].op
|
170
|
+
if (rOp[0] !== wOp[0] || rOp[1] !== wOp[1]) {
|
171
|
+
return [rOpChoices[i].ops.filter((op) => !(op[0] === rOp[0] && op[1] === rOp[1])).concat([rOp])]
|
172
|
+
}
|
173
|
+
}
|
174
|
+
return []
|
175
|
+
}
|
176
|
+
|
177
|
+
const processContexts = (contexts, params) => {
|
178
|
+
const contextsPrime = []
|
179
|
+
const generated = []
|
180
|
+
const logs = []
|
181
|
+
for (const context of contexts) {
|
182
|
+
const result = processContext(context, Object.assign({}, params, { logs }))
|
183
|
+
contextsPrime.push(result.context)
|
184
|
+
generated.push(result.generated)
|
185
|
+
}
|
186
|
+
return { contexts: contextsPrime, generated, logs }
|
187
|
+
}
|
188
|
+
|
189
|
+
const getObjects = (objects) => (uuid) => {
|
190
|
+
if (objects && objects.namespaced) {
|
191
|
+
objects = objects.namespaced[uuid]
|
192
|
+
}
|
193
|
+
return objects
|
194
|
+
}
|
195
|
+
|
196
|
+
const processContext = (context, { objects = {}, config, logs = [] }) => {
|
197
|
+
const generators = config.getGenerators(logs)
|
198
|
+
const semantics = config.getSemantics(logs)
|
199
|
+
|
200
|
+
// map to hash
|
201
|
+
config = config || {}
|
202
|
+
if (config.config) {
|
203
|
+
config = config
|
204
|
+
}
|
205
|
+
|
206
|
+
const response = {} // NA but passed in
|
207
|
+
// generators = new Generators(generators.map((g) => new Generator(normalizeGenerator(g))))
|
208
|
+
// semantics = new Semantics(semantics.map((g) => new Semantic(normalizeSemantic(g))))
|
209
|
+
const hierarchy = new Digraph((config.config || {}).hierarchy || [])
|
210
|
+
|
211
|
+
const args = { objects, response, getObjects: getObjects(objects) }
|
212
|
+
setupArgs(args, config, logs, hierarchy)
|
213
|
+
|
214
|
+
context = semantics.apply(args, context)
|
215
|
+
const generated = generators.apply(args, context)[0]
|
216
|
+
const paraphrases = generators.apply(args, context, { paraphrase: true, response: false, isResponse: false })[0]
|
217
|
+
let responses = []
|
218
|
+
if (context.isResponse) {
|
219
|
+
responses = generated
|
220
|
+
}
|
221
|
+
return { context, generated, paraphrases, responses }
|
222
|
+
}
|
223
|
+
|
224
|
+
const convertToStable = (objects) => {
|
225
|
+
const stableObjects = Object.assign({}, objects)
|
226
|
+
stableObjects.namespaced = {}
|
227
|
+
let counter = 0
|
228
|
+
Object.keys(((objects || {}).namespaced || {})).forEach((uuid) => {
|
229
|
+
stableObjects.namespaced[`${counter}`] = objects.namespaced[uuid]
|
230
|
+
counter += 1
|
231
|
+
})
|
232
|
+
return stableObjects
|
233
|
+
}
|
234
|
+
|
235
|
+
const writeTestFile = (fn, tests) => {
|
236
|
+
const stabilize = (tests) => {
|
237
|
+
for (let test of tests) {
|
238
|
+
for (opChoice of test.metadata.opChoices) {
|
239
|
+
opChoice.ops.sort()
|
240
|
+
}
|
241
|
+
test.metadata.opChoices.sort()
|
242
|
+
}
|
243
|
+
}
|
244
|
+
stabilize(tests)
|
245
|
+
fs.writeFileSync(fn, stringify(tests, { space: 2 }), { encoding: 'utf8', flag: 'w+' })
|
246
|
+
}
|
247
|
+
|
248
|
+
const writeTest = (fn, query, objects, generated, paraphrases, responses, contexts, associations, metadata, config, saveDeveloper) => {
|
249
|
+
let tests = []
|
250
|
+
if (fs.existsSync(fn)) {
|
251
|
+
tests = JSON.parse(fs.readFileSync(fn))
|
252
|
+
}
|
253
|
+
for (let association of associations) {
|
254
|
+
association.sort()
|
255
|
+
}
|
256
|
+
associations.sort()
|
257
|
+
// tests[query] = sortJson({ paraphrases, responses, contexts, objects: convertToStable(objects), associations, metadata, config, developerTest: saveDeveloper }, { depth: 25 })
|
258
|
+
results = sortJson({ query, paraphrases, responses, contexts, objects: convertToStable(objects), associations, metadata, config, developerTest: saveDeveloper }, { depth: 25 })
|
259
|
+
let wasSet = false;
|
260
|
+
tests.forEach( (test, index) => {
|
261
|
+
if (test.query == query) {
|
262
|
+
tests[index] = results
|
263
|
+
wasSet = true
|
264
|
+
}
|
265
|
+
});
|
266
|
+
if (!wasSet) {
|
267
|
+
tests.push(results)
|
268
|
+
}
|
269
|
+
// fs.writeFileSync(fn, stringify(tests, { space: 2 }), { encoding: 'utf8', flag: 'w+' })
|
270
|
+
writeTestFile(fn, tests)
|
271
|
+
}
|
272
|
+
|
273
|
+
const processContextsB = ({ config, hierarchy, semantics, generators, json, isTest }) => {
|
274
|
+
// TODO fix this name to contextsPrime
|
275
|
+
const contextsPrime = []
|
276
|
+
const generatedPrime = []
|
277
|
+
const paraphrasesPrime = []
|
278
|
+
const responsesPrime = []
|
279
|
+
const contexts = []
|
280
|
+
let first = true
|
281
|
+
contexts.push({ marker: 'controlStart', controlRemove: true })
|
282
|
+
for (const context of json.contexts) {
|
283
|
+
if (first) {
|
284
|
+
first = false
|
285
|
+
} else {
|
286
|
+
contexts.push({ marker: 'controlBetween', controlRemove: true })
|
287
|
+
}
|
288
|
+
contexts.push(context)
|
289
|
+
}
|
290
|
+
contexts.push({ marker: 'controlEnd', controlRemove: true })
|
291
|
+
const objects = config.get('objects')
|
292
|
+
const args = { objects, isResponse: true, response: json, isTest, getObjects: getObjects(objects) }
|
293
|
+
setupArgs(args, config, json.logs, hierarchy)
|
294
|
+
const toDo = [...contexts]
|
295
|
+
args.insert = (context) => toDo.unshift(context)
|
296
|
+
while (toDo.length > 0) {
|
297
|
+
args.calls.next()
|
298
|
+
const context = toDo.shift()
|
299
|
+
let contextPrime = context
|
300
|
+
context.topLevel = true
|
301
|
+
try {
|
302
|
+
if (json.has_errors) {
|
303
|
+
throw new Error('There are errors in the logs')
|
304
|
+
}
|
305
|
+
if (!config.get('skipSemantics')) {
|
306
|
+
if (!config.doMotivations(args, context)) {
|
307
|
+
const semantics = config.getSemantics(json.logs)
|
308
|
+
try {
|
309
|
+
contextPrime = semantics.apply(args, context)
|
310
|
+
} catch( e ) {
|
311
|
+
if (e.message == 'Maximum call stack size exceeded') {
|
312
|
+
const mostCalled = semantics.getMostCalled()
|
313
|
+
e.message += `\nThe most called semantic was:\nnotes: ${mostCalled.notes}\nmatch: ${mostCalled.matcher.toString()}\napply: ${mostCalled._apply.toString()}\n`
|
314
|
+
}
|
315
|
+
throw e;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
if (contextPrime.controlRemove) {
|
320
|
+
continue
|
321
|
+
}
|
322
|
+
let assumed = { isResponse: true };
|
323
|
+
const generated = config.getGenerators(json.logs).apply(args, contextPrime, assumed)[0]
|
324
|
+
// assumed = { paraphrase: true, response: false };
|
325
|
+
assumed = { paraphrase: true };
|
326
|
+
args.g = (c) => config.getGenerators(json.logs).apply(args, c, assumed)
|
327
|
+
args.gp = (c) => config.getGenerators(json.logs).apply(args, {...c, paraphrase: true, isResponse: false, response: false }, assumed)
|
328
|
+
args.gr = (c) => config.getGenerators(json.logs).apply(args, {...c, paraphrase: false }, assumed)
|
329
|
+
args.gs = gs(args.g)
|
330
|
+
args.gsp = gs(args.gsp)
|
331
|
+
args.gsr = gs(args.gr)
|
332
|
+
const paraphrases = config.getGenerators(json.logs).apply(args, contextPrime, assumed)[0]
|
333
|
+
args.g = (c) => config.getGenerators(json.logs).apply(args, c)
|
334
|
+
args.gp = (c) => config.getGenerators(json.logs).apply(args, {...c, paraphrase: true, isResponse: false, response: false })
|
335
|
+
args.gr = (c) => config.getGenerators(json.logs).apply(args, {...c, paraphrase: false })
|
336
|
+
args.gs = gs(args.g)
|
337
|
+
args.gsp = gs(args.gp)
|
338
|
+
args.gsr = gs(args.gr)
|
339
|
+
contextsPrime.push(contextPrime)
|
340
|
+
generatedPrime.push(generated)
|
341
|
+
paraphrasesPrime.push(paraphrases)
|
342
|
+
if (contextPrime.isResponse) {
|
343
|
+
responsesPrime.push(generated)
|
344
|
+
} else {
|
345
|
+
responsesPrime.push('')
|
346
|
+
}
|
347
|
+
|
348
|
+
// add results to processed list
|
349
|
+
config.config.objects.processed = config.config.objects.processed || []
|
350
|
+
config.config.objects.processed = config.config.objects.processed.slice(0, 5)
|
351
|
+
config.config.objects.processed.unshift({ context: contextPrime, paraphrases: paraphrases, responses: responsesPrime })
|
352
|
+
} catch (e) {
|
353
|
+
if (Array.isArray(e)) {
|
354
|
+
e = {
|
355
|
+
errors: e
|
356
|
+
}
|
357
|
+
}
|
358
|
+
e.context = contextPrime
|
359
|
+
if (e.logs) {
|
360
|
+
e.logs = e.logs.concat(json.logs)
|
361
|
+
} else {
|
362
|
+
e.logs = json.logs
|
363
|
+
}
|
364
|
+
e.metadata = json.metadata
|
365
|
+
if (json.trace) {
|
366
|
+
e.trace = json.trace
|
367
|
+
}
|
368
|
+
throw e
|
369
|
+
}
|
370
|
+
}
|
371
|
+
return { contextsPrime, generatedPrime, paraphrasesPrime, responsesPrime }
|
372
|
+
}
|
373
|
+
|
374
|
+
const doWithRetries = async (n, url, data) => {
|
375
|
+
for (let i = 0; i < n; ++i) {
|
376
|
+
const result = await fetch(`${url}/process`, {
|
377
|
+
method: 'POST',
|
378
|
+
body: JSON.stringify(data),
|
379
|
+
timeout: 1000 * 60 * 5, // it does not respect this timeout so that's why I have the retries
|
380
|
+
headers: {
|
381
|
+
mode: 'no-cors',
|
382
|
+
'Content-Type': 'application/json'
|
383
|
+
}
|
384
|
+
})
|
385
|
+
if (result.ok) {
|
386
|
+
return JSON.parse(JSON.stringify(await result.json()))
|
387
|
+
}
|
388
|
+
if (result.status === 504) {
|
389
|
+
if (n === 0) {
|
390
|
+
throw `Error ${result.status} - ${result.statusText}`
|
391
|
+
} else {
|
392
|
+
continue
|
393
|
+
}
|
394
|
+
}
|
395
|
+
if (result.status >= 500 && result.status < 600) {
|
396
|
+
throw `Error ${result.status} - ${result.statusText}. If you are using the researcher version try restarting your entodicton server. If takes a few minutes to be up.`
|
397
|
+
} else {
|
398
|
+
throw `Error ${result.status} - ${result.statusText}`
|
399
|
+
}
|
400
|
+
}
|
401
|
+
}
|
402
|
+
|
403
|
+
const setupProcessB = ({ config }) => {
|
404
|
+
const key = config._key
|
405
|
+
|
406
|
+
const data = Object.assign({ key, version: '3' }, config.config)
|
407
|
+
for (const uuid of Object.keys(data.namespaces)) {
|
408
|
+
const km = config.configs.find((km) => km.uuid === uuid)
|
409
|
+
data.namespaces[uuid].name = km.name
|
410
|
+
}
|
411
|
+
|
412
|
+
// const generators = new Generators((data.generators || []).map((g) => new Generator(normalizeGenerator(g))))
|
413
|
+
delete data.generators
|
414
|
+
// const semantics = new Semantics((data.semantics || []).map((g) => new Semantic(normalizeSemantic(g))))
|
415
|
+
delete data.semantics
|
416
|
+
const hierarchy = new Digraph((config.config || {}).hierarchy || [])
|
417
|
+
|
418
|
+
return {
|
419
|
+
data,
|
420
|
+
// generators,
|
421
|
+
// semantics,
|
422
|
+
hierarchy
|
423
|
+
}
|
424
|
+
}
|
425
|
+
|
426
|
+
// instance template
|
427
|
+
const processInstance = (config, instance) => {
|
428
|
+
const transitoryMode = global.transitoryMode
|
429
|
+
global.transitoryMode = false
|
430
|
+
const { /* data, generators, semantics, */ hierarchy } = setupProcessB({ config })
|
431
|
+
for (const results of (instance.resultss || [])) {
|
432
|
+
if (results.extraConfig) {
|
433
|
+
// config.addInternal(results, useOldVersion = true, skipObjects = false, includeNamespaces = true, allowNameToBeNull = false)
|
434
|
+
config.addInternal(results)
|
435
|
+
} else {
|
436
|
+
processContextsB({ config, hierarchy, json: results/*, generators, semantics */ })
|
437
|
+
}
|
438
|
+
}
|
439
|
+
global.transitoryMode = transitoryMode
|
440
|
+
}
|
441
|
+
|
442
|
+
const _process = async (config, query, { credentials, writeTests, isTest, saveDeveloper, testConfig, testsFN, errorHandler = defaultErrorHandler, beforeQuery } = {}) => {
|
443
|
+
if (credentials) {
|
444
|
+
config.server(credentials.server, credentials.key)
|
445
|
+
}
|
446
|
+
const url = config._server
|
447
|
+
const retries = 2
|
448
|
+
writeTests = !!writeTests
|
449
|
+
|
450
|
+
// ensure same start state
|
451
|
+
try {
|
452
|
+
if (writeTests) {
|
453
|
+
config.rebuild()
|
454
|
+
const objects = getObjects(config.config.objects)(config.uuid)
|
455
|
+
beforeQuery({ query, isModule: false, objects })
|
456
|
+
}
|
457
|
+
} catch(error) {
|
458
|
+
throw error
|
459
|
+
}
|
460
|
+
|
461
|
+
const { data, /* generators, semantics, */ hierarchy } = setupProcessB({ config })
|
462
|
+
let queries = query.split('\\n')
|
463
|
+
|
464
|
+
try {
|
465
|
+
const response = {
|
466
|
+
hierarchy: [],
|
467
|
+
load_cache_time: 0.0,
|
468
|
+
logs: [],
|
469
|
+
metadata: {
|
470
|
+
opChoices: []
|
471
|
+
},
|
472
|
+
times: 0.0,
|
473
|
+
trace: '',
|
474
|
+
contexts: [],
|
475
|
+
generated: [],
|
476
|
+
paraphrases: [],
|
477
|
+
responses: [],
|
478
|
+
associations: [],
|
479
|
+
}
|
480
|
+
|
481
|
+
while (true) {
|
482
|
+
if (queries.length === 0) {
|
483
|
+
break;
|
484
|
+
}
|
485
|
+
data.utterance = queries[0]
|
486
|
+
const json = await doWithRetries(retries, url, data)
|
487
|
+
json.contexts = json.results
|
488
|
+
delete json.results
|
489
|
+
if (json.status !== 200) {
|
490
|
+
throw json
|
491
|
+
} else {
|
492
|
+
const { contextsPrime, generatedPrime, paraphrasesPrime, responsesPrime } =
|
493
|
+
processContextsB({ isTest, config, hierarchy, json/*, generators, semantics */ })
|
494
|
+
response.associations = json.associations
|
495
|
+
response.hierarchy = json.hierarchy
|
496
|
+
response.load_cache_time += json.load_cache_time
|
497
|
+
appendNoDups(response.logs, json.logs)
|
498
|
+
response.memory_free_percent = json.memory_free_percent
|
499
|
+
// appendNoDups(response.metadata.associations, json.metadata.associations)
|
500
|
+
// appendNoDups(response.metadata.priorities, json.metadata.priorities)
|
501
|
+
appendNoDups(response.metadata.opChoices, json.metadata.opChoices)
|
502
|
+
response.times += json.times
|
503
|
+
response.trace = response.trace.concat(json.trace)
|
504
|
+
response.version = json.version
|
505
|
+
|
506
|
+
response.contexts = response.contexts.concat(contextsPrime)
|
507
|
+
response.generated = response.generated.concat(generatedPrime)
|
508
|
+
response.paraphrases = response.paraphrases.concat(paraphrasesPrime)
|
509
|
+
response.responses = response.responses.concat(responsesPrime)
|
510
|
+
queries = queries.slice(1)
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
if (writeTests) {
|
515
|
+
const actual_config = getConfigForTest(config, testConfig)
|
516
|
+
writeTest(testsFN, query, config.config.objects, response.generated, response.paraphrases, response.responses, response.contexts, response.associations, response.metadata, actual_config, saveDeveloper)
|
517
|
+
}
|
518
|
+
|
519
|
+
return response
|
520
|
+
} catch(error) {
|
521
|
+
error.query = query
|
522
|
+
throw error
|
523
|
+
}
|
524
|
+
}
|
525
|
+
|
526
|
+
const getConfigForTest = (config, testConfig) => {
|
527
|
+
const includes = testConfig.include || testConfig.includes
|
528
|
+
if (!includes) {
|
529
|
+
return {}
|
530
|
+
}
|
531
|
+
const configForTest = {}
|
532
|
+
for (const key of Object.keys(includes)) {
|
533
|
+
// configForTest[key] = config.config[key]
|
534
|
+
if (key === 'words') {
|
535
|
+
const words = config.config.words
|
536
|
+
configForTest.words = {}
|
537
|
+
for (const key of Object.keys(words)) {
|
538
|
+
const defs = []
|
539
|
+
for (const def of words[key]) {
|
540
|
+
// TODO handle thie uuids the right way
|
541
|
+
defs.push(Object.assign({}, def, { uuid: undefined }))
|
542
|
+
}
|
543
|
+
configForTest.words[key] = defs
|
544
|
+
}
|
545
|
+
} else if (key === 'operators') {
|
546
|
+
configForTest.operators = config.config.operators.map((operator) => Object.assign({}, operator, { uuid: undefined }))
|
547
|
+
} else if (key === 'bridges') {
|
548
|
+
configForTest.bridges = config.config.bridges.map((bridge) => Object.assign({}, bridge, { uuid: undefined }))
|
549
|
+
} else {
|
550
|
+
configForTest[key] = config.config[key]
|
551
|
+
}
|
552
|
+
}
|
553
|
+
return configForTest
|
554
|
+
}
|
555
|
+
|
556
|
+
const runTest = async (config, expected, { verbose, beforeQuery, afterTest, testConfig, debug }) => {
|
557
|
+
const test = expected.query
|
558
|
+
// initialize in between test so state is not preserved since the test was adding without state
|
559
|
+
config.rebuild()
|
560
|
+
config.addAssociationsFromTests(config.tests)
|
561
|
+
// config.addAssocationsFromTests(
|
562
|
+
const errorHandler = (error) => {
|
563
|
+
if (error.metadata) {
|
564
|
+
const priorities = analyzeMetaData(expected.metadata, error.metadata)
|
565
|
+
if (priorities.length > 0) {
|
566
|
+
const log = `Hint, if the results are flakey try adding the specified priorities ${JSON.stringify(priorities)}`
|
567
|
+
error.logs.push(log)
|
568
|
+
}
|
569
|
+
}
|
570
|
+
defaultErrorHandler(error)
|
571
|
+
}
|
572
|
+
|
573
|
+
const objects = getObjects(config.config.objects)(config.uuid)
|
574
|
+
beforeQuery({ query: test, isModule: false, objects })
|
575
|
+
config.resetMotivations()
|
576
|
+
try {
|
577
|
+
const result = await _process(config, test, { errorHandler, isTest: true })
|
578
|
+
result.query = test
|
579
|
+
if (debug) {
|
580
|
+
defaultInnerProcess(config, errorHandler, result)
|
581
|
+
}
|
582
|
+
if (verbose) {
|
583
|
+
const widths = [100, 20]
|
584
|
+
const lines = new Lines(widths)
|
585
|
+
lines.setElement(0, 0, test)
|
586
|
+
lines.setElement(0, 1, `time on server ${result.times.toFixed(2)}`)
|
587
|
+
lines.log()
|
588
|
+
}
|
589
|
+
const expected_objects = sortJson(convertToStable(expected.objects), { depth: 25 })
|
590
|
+
const actual_objects = sortJson(convertToStable(config.config.objects), { depth: 25 })
|
591
|
+
const failed_paraphrases = !matching(result.paraphrases, expected.paraphrases)
|
592
|
+
const failed_responses = !matching(result.responses, expected.responses)
|
593
|
+
const failed_contexts = !matching(result.contexts, expected.contexts)
|
594
|
+
const failed_objects = !matching(actual_objects, expected_objects)
|
595
|
+
const actual_config = sortJson(convertToStable(getConfigForTest(config, testConfig)), { depth: 25 })
|
596
|
+
const expected_config = sortJson(convertToStable(expected.config), { depth: 25 })
|
597
|
+
const failed_config = !matching(actual_config, expected_config)
|
598
|
+
let failed = failed_paraphrases || failed_responses || failed_contexts || failed_objects || failed_config
|
599
|
+
if (!failed) {
|
600
|
+
if (afterTest) {
|
601
|
+
failed = afterTest({ query: test, expected, actual: result, config })
|
602
|
+
if (failed) {
|
603
|
+
return {
|
604
|
+
utterance: test,
|
605
|
+
errorFromAfterTest: failed,
|
606
|
+
expected: { responses: expected.responses, paraphrases: expected.paraphrases, results: expected.contexts, objects: expected_objects, config: expected.config },
|
607
|
+
actual: { responses: result.responses, paraphrases: result.paraphrases, results: result.contexts, objects: actual_objects, config: actual_config }
|
608
|
+
}
|
609
|
+
}
|
610
|
+
}
|
611
|
+
}
|
612
|
+
|
613
|
+
if (expected.metadata && result.metadata && failed) {
|
614
|
+
const priorities = analyzeMetaData(expected.metadata, result.metadata)
|
615
|
+
if (priorities.length > 0) {
|
616
|
+
const log = `Hint, if the results are flakey try adding the specified priorities ${JSON.stringify(priorities)}`
|
617
|
+
result.logs.push(log)
|
618
|
+
}
|
619
|
+
}
|
620
|
+
if (failed) {
|
621
|
+
return {
|
622
|
+
utterance: test,
|
623
|
+
expected: { responses: expected.responses, paraphrases: expected.paraphrases, results: expected.contexts, objects: expected_objects, config: expected.config },
|
624
|
+
actual: { responses: result.responses, paraphrases: result.paraphrases, results: result.contexts, objects: actual_objects, config: actual_config }
|
625
|
+
}
|
626
|
+
}
|
627
|
+
} catch(error) {
|
628
|
+
if (verbose) {
|
629
|
+
console.log(test)
|
630
|
+
}
|
631
|
+
if (error.metadata) {
|
632
|
+
const priorities = analyzeMetaData(expected.metadata, error.metadata)
|
633
|
+
if (priorities.length > 0) {
|
634
|
+
const log = `Hint, if the results are flakey try adding the specified priorities ${JSON.stringify(priorities)}`
|
635
|
+
error.logs.push(log)
|
636
|
+
}
|
637
|
+
}
|
638
|
+
throw error
|
639
|
+
}
|
640
|
+
}
|
641
|
+
|
642
|
+
const runTestsHelper = async (config, tests, failed, juicyBits) => {
|
643
|
+
const { stopAtFirstError } = juicyBits
|
644
|
+
while (true) {
|
645
|
+
if (tests.length === 0) {
|
646
|
+
return Promise.resolve(failed)
|
647
|
+
}
|
648
|
+
const test = tests.shift()
|
649
|
+
const result = await runTest(config, test, juicyBits)
|
650
|
+
if (result != null) {
|
651
|
+
failed.push(result)
|
652
|
+
if (stopAtFirstError) {
|
653
|
+
return failed
|
654
|
+
}
|
655
|
+
}
|
656
|
+
}
|
657
|
+
}
|
658
|
+
|
659
|
+
const runTests = async (config, testFile, juicyBits) => {
|
660
|
+
const tests = JSON.parse(fs.readFileSync(testFile))
|
661
|
+
const { beforeTests, afterTests } = juicyBits
|
662
|
+
beforeTests()
|
663
|
+
if (juicyBits.verbose) {
|
664
|
+
console.log('\n', testFile, '-----------------------------------------------', '\n')
|
665
|
+
}
|
666
|
+
const v = await runTestsHelper(config, [...tests], [], juicyBits)
|
667
|
+
afterTests()
|
668
|
+
return v
|
669
|
+
}
|
670
|
+
|
671
|
+
const saveTest = async (testFile, config, test, expected, beforeQuery, testConfig, saveDeveloper) => {
|
672
|
+
config.rebuild()
|
673
|
+
const objects = getObjects(config.config.objects)(config.uuid)
|
674
|
+
config.resetMotivations()
|
675
|
+
beforeQuery({ query: test, isModule: false, objects })
|
676
|
+
console.log(test)
|
677
|
+
const result = await _process(config, test, { isTest: true })
|
678
|
+
// const actualObjects = config.config.objects
|
679
|
+
const actualConfig = getConfigForTest(config, testConfig)
|
680
|
+
writeTest(testFile, test, config.config.objects, result.generated, result.paraphrases, result.responses, result.contexts, result.associations, result.metadata, actualConfig, saveDeveloper)
|
681
|
+
}
|
682
|
+
|
683
|
+
const saveTestsHelper = async (testFile, config, tests, todo, beforeQuery, testConfig, saveDeveloper) => {
|
684
|
+
if (todo.length === 0) {
|
685
|
+
return
|
686
|
+
}
|
687
|
+
const test = todo.pop()
|
688
|
+
config.rebuild()
|
689
|
+
const result = await saveTest(testFile, config, test, tests[test], beforeQuery, testConfig, saveDeveloper)
|
690
|
+
// initialize in between test so state is not preserved since the test was adding without state
|
691
|
+
// config.initialize({force: true})
|
692
|
+
config.rebuild()
|
693
|
+
return saveTestsHelper(testFile, config, tests, todo, beforeQuery, testConfig, saveDeveloper)
|
694
|
+
}
|
695
|
+
|
696
|
+
const saveTests = (config, testFile, beforeQuery, testConfig) => {
|
697
|
+
const tests = JSON.parse(fs.readFileSync(testFile))
|
698
|
+
console.log(testFile)
|
699
|
+
return saveTestsHelper(testFile, config, tests, tests.map( (test) => test.query ), beforeQuery, testConfig)
|
700
|
+
}
|
701
|
+
|
702
|
+
/*
|
703
|
+
const showExamples = (testFile) => {
|
704
|
+
const tests = JSON.parse(fs.readFileSync(testFile))
|
705
|
+
Object.keys(tests).forEach((test) => console.log(test))
|
706
|
+
}
|
707
|
+
*/
|
708
|
+
|
709
|
+
const showInfo = (description, section, config) => {
|
710
|
+
const name = config.name
|
711
|
+
const includes = config.configs.slice(1).map((km) => km.config.name)
|
712
|
+
const visibleExamples = []
|
713
|
+
for (const test of config.tests) {
|
714
|
+
if (!test.developerTest) {
|
715
|
+
visibleExamples.push(test.query)
|
716
|
+
}
|
717
|
+
}
|
718
|
+
const info = { name, description, examples: visibleExamples, section, includes, demo: config.demo }
|
719
|
+
if (config.instances.length > 0) {
|
720
|
+
info.template = {
|
721
|
+
base: config.instances[0].base
|
722
|
+
}
|
723
|
+
}
|
724
|
+
console.log(JSON.stringify(info, null, 2))
|
725
|
+
}
|
726
|
+
|
727
|
+
const submitBugToAPI = async (subscription_id, subscription_password, config) => {
|
728
|
+
console.log('********* Submitting bug *********')
|
729
|
+
const body = { description: config.config.description, config: config.config }
|
730
|
+
// fetch('http://localhost:5000/bug', {
|
731
|
+
fetch('https://thinktelligence.com/api/bug', {
|
732
|
+
method: 'POST',
|
733
|
+
body: JSON.stringify(body),
|
734
|
+
headers: {
|
735
|
+
mode: 'no-cors', // Type of mode of the request
|
736
|
+
'Content-Type': 'application/json', // request content type
|
737
|
+
Authorization: 'Basic ' + base64.encode(subscription_id + ':' + subscription_password)
|
738
|
+
}
|
739
|
+
}).then((result) => result.json())
|
740
|
+
.then((json) => {
|
741
|
+
if (json.status === 404) {
|
742
|
+
console.log(`Error submitting the bug: ${json.status} ${json.statusText}`)
|
743
|
+
} else {
|
744
|
+
console.log(`New bug id id ${json.id}`)
|
745
|
+
}
|
746
|
+
})
|
747
|
+
}
|
748
|
+
|
749
|
+
const submitBug = async (subscription_id, subscription_password, config, utterance, retries = 2) => {
|
750
|
+
// TODO remove these from the config
|
751
|
+
const properties = ['expected_contexts', 'expected_generated']
|
752
|
+
properties.forEach((property) => {
|
753
|
+
if (!config.get('expected_contexts')) {
|
754
|
+
throw 'Missing property expected_contexts'
|
755
|
+
}
|
756
|
+
})
|
757
|
+
|
758
|
+
return _process(config, utterance)
|
759
|
+
.then((responses) => {
|
760
|
+
let hasError = false
|
761
|
+
if (!matching(responses.contexts, config.config.expected_contexts)) {
|
762
|
+
console.log('JSON does not match')
|
763
|
+
console.log('actual', JSON.stringify(responses.contexts))
|
764
|
+
console.log('expected', JSON.stringify(config.config.expected_contexts))
|
765
|
+
hasError = true
|
766
|
+
}
|
767
|
+
if (!matching(responses.generated, config.config.expected_generated)) {
|
768
|
+
console.log('Generated does not match')
|
769
|
+
console.log('actual', JSON.stringify(responses.generated))
|
770
|
+
console.log('expected', JSON.stringify(config.config.expected_generated))
|
771
|
+
hasError = true
|
772
|
+
}
|
773
|
+
|
774
|
+
if (hasError) {
|
775
|
+
submitBugToAPI(subscription_id, subscription_password, config)
|
776
|
+
} else {
|
777
|
+
throw '\n\n *** The expected contexts matched the generated contexts so the bug was not submitted since its working. ***\n\n:'
|
778
|
+
}
|
779
|
+
})
|
780
|
+
.catch((error) => {
|
781
|
+
console.log('Error', error)
|
782
|
+
throw error
|
783
|
+
})
|
784
|
+
}
|
785
|
+
|
786
|
+
const defaultErrorHandler = async (error) => {
|
787
|
+
if (error.logs) {
|
788
|
+
console.log('\nlogs: ')
|
789
|
+
for (const log of error.logs) {
|
790
|
+
console.log('\n ', log)
|
791
|
+
}
|
792
|
+
}
|
793
|
+
|
794
|
+
if (error.trace) {
|
795
|
+
console.log('trace: ', error.trace)
|
796
|
+
}
|
797
|
+
|
798
|
+
if (error.config) {
|
799
|
+
console.log('objects', util.inspect(error.config.get('objects'), { depth: Infinity, sorted: true }))
|
800
|
+
}
|
801
|
+
|
802
|
+
if (error.stack) {
|
803
|
+
console.log('error: ')
|
804
|
+
console.log(error.stack)
|
805
|
+
}
|
806
|
+
|
807
|
+
if (error.errors) {
|
808
|
+
console.log('error: ')
|
809
|
+
for (const e of error.errors) {
|
810
|
+
if (e.logs) {
|
811
|
+
console.log('\nlogs: ')
|
812
|
+
for (const log of e.logs) {
|
813
|
+
console.log('\n ', log)
|
814
|
+
}
|
815
|
+
}
|
816
|
+
if (e.error) {
|
817
|
+
console.log('\n ', e.error)
|
818
|
+
} else {
|
819
|
+
console.log('\n ', e)
|
820
|
+
}
|
821
|
+
}
|
822
|
+
}
|
823
|
+
if (error.error) {
|
824
|
+
console.log('error: ')
|
825
|
+
for (const e of error.error) {
|
826
|
+
console.log('\n ', e)
|
827
|
+
}
|
828
|
+
}
|
829
|
+
|
830
|
+
if (error.query) {
|
831
|
+
console.log('query: ', error.query)
|
832
|
+
}
|
833
|
+
|
834
|
+
// throw error
|
835
|
+
}
|
836
|
+
|
837
|
+
const defaultInnerProcess = (config, errorHandler, responses) => {
|
838
|
+
if (responses.errors) {
|
839
|
+
console.log('Errors')
|
840
|
+
responses.errors.forEach((error) => console.log(` ${error}`))
|
841
|
+
}
|
842
|
+
console.log("KM's loaded", config.configs.map((c) => c.name))
|
843
|
+
console.log('This is the global objects from running semantics:\n', config.objects)
|
844
|
+
if (responses.logs) {
|
845
|
+
console.log('Logs')
|
846
|
+
responses.logs.forEach((log) => console.log(` ${log}`))
|
847
|
+
}
|
848
|
+
console.log(responses.trace)
|
849
|
+
|
850
|
+
if (global.beforeObjects) {
|
851
|
+
console.log('objects', diffString(global.beforeObjects, config.get('objects')))
|
852
|
+
} else {
|
853
|
+
console.log('objects', util.inspect(config.get('objects'), { depth: Infinity, sorted: true }))
|
854
|
+
}
|
855
|
+
console.log('--- The contexts are ----------')
|
856
|
+
console.log(JSON.stringify(sortJson(responses.contexts, { depth: 25 }), null, 2))
|
857
|
+
console.log('')
|
858
|
+
/*
|
859
|
+
console.log('--- The generated strings are ----------')
|
860
|
+
for (const response of responses.generated) {
|
861
|
+
console.log(response)
|
862
|
+
}
|
863
|
+
*/
|
864
|
+
console.log('')
|
865
|
+
const widths = [60, 10, 72]
|
866
|
+
const lines = new Lines(widths)
|
867
|
+
lines.setElement(0, 0, '--- The paraphrases are ----------')
|
868
|
+
lines.setElement(0, 2, '--- The response strings are ----------')
|
869
|
+
lines.log()
|
870
|
+
for (let i = 0; i < responses.paraphrases.length; ++i) {
|
871
|
+
// dont show events
|
872
|
+
if (responses.contexts[i].hidden) {
|
873
|
+
continue
|
874
|
+
}
|
875
|
+
lines.setElement(0, 0, responses.paraphrases[i])
|
876
|
+
if ((responses.responses[i] || []).length > 0) {
|
877
|
+
lines.setElement(0, 2, responses.responses[i])
|
878
|
+
} else {
|
879
|
+
lines.setElement(0, 2, '')
|
880
|
+
}
|
881
|
+
lines.log()
|
882
|
+
}
|
883
|
+
}
|
884
|
+
|
885
|
+
const defaultProcess = ({ config, errorHandler }) => async (promise) => {
|
886
|
+
try {
|
887
|
+
const responses = await promise
|
888
|
+
defaultInnerProcess(config, errorHandler, responses)
|
889
|
+
} catch(error) {
|
890
|
+
error.config = config
|
891
|
+
defaultErrorHandler(error)
|
892
|
+
}
|
893
|
+
}
|
894
|
+
|
895
|
+
/*
|
896
|
+
const kmFileTemplate = (kmBaseName, kmName) =>
|
897
|
+
`const entodicton = require('entodicton')
|
898
|
+
const base = require('./${kmBaseName}').copy()
|
899
|
+
const ${kmName}_tests = require('./${kmName}.test.json')
|
900
|
+
const ${kmName}_instance = require('./${kmBaseName}.${kmName}.instance.json')
|
901
|
+
|
902
|
+
const config = new entodicton.Config({ name: '${kmName}' })
|
903
|
+
config.add(base)
|
904
|
+
kirk_instance.base = '${kmBaseName}'
|
905
|
+
config.load(${kmName}_instance)
|
906
|
+
|
907
|
+
entodicton.knowledgeModule( {
|
908
|
+
module,
|
909
|
+
description: '${kmName} related concepts',
|
910
|
+
section,
|
911
|
+
config,
|
912
|
+
test: {
|
913
|
+
name: './${kmName}.test.json',
|
914
|
+
contents: ${kmName}_tests
|
915
|
+
},
|
916
|
+
})
|
917
|
+
`
|
918
|
+
*/
|
919
|
+
|
920
|
+
const build = async ({ config, target, beforeQuery, template, errorHandler = defaultErrorHandler }) => {
|
921
|
+
const accumulators = {
|
922
|
+
resultss: [],
|
923
|
+
fragments: [],
|
924
|
+
semantics: [],
|
925
|
+
associations: [],
|
926
|
+
}
|
927
|
+
const looper = (queries) => {
|
928
|
+
if (queries.length === 0) {
|
929
|
+
finish()
|
930
|
+
return
|
931
|
+
}
|
932
|
+
const { property, hierarchy, query: queryOrExtraConfig, skipSemantics } = queries.shift()
|
933
|
+
// queries are strings or { query: "blah", development: true/false }
|
934
|
+
if (typeof queryOrExtraConfig === 'string' || queryOrExtraConfig.query) {
|
935
|
+
let query = queryOrExtraConfig;
|
936
|
+
if (typeof queryOrExtraConfig === 'string') {
|
937
|
+
query = { query }
|
938
|
+
}
|
939
|
+
console.log('query', query.query)
|
940
|
+
config.config.skipSemantics = skipSemantics
|
941
|
+
const transitoryMode = global.transitoryMode
|
942
|
+
if (property == 'fragments') {
|
943
|
+
global.transitoryMode = true
|
944
|
+
}
|
945
|
+
// greg32
|
946
|
+
// config.addInternal( query )
|
947
|
+
if (hierarchy) {
|
948
|
+
for (let edge of hierarchy) {
|
949
|
+
if (Array.isArray(edge)) {
|
950
|
+
config.addHierarchy(edge[0], edge[1])
|
951
|
+
} else {
|
952
|
+
config.addHierarchy(edge)
|
953
|
+
}
|
954
|
+
}
|
955
|
+
}
|
956
|
+
|
957
|
+
try {
|
958
|
+
const results = _process(config, query.query, { beforeQuery })
|
959
|
+
if (config.config.debug) {
|
960
|
+
// TODO pass in the error handler like the other ones
|
961
|
+
defaultInnerProcess(config, defaultErrorHandler, results)
|
962
|
+
}
|
963
|
+
global.transitoryMode = transitoryMode
|
964
|
+
config.config.skipSemantics = null
|
965
|
+
results.query = query.query
|
966
|
+
results.development = query.development
|
967
|
+
results.key = { query: query.query, hierarchy }
|
968
|
+
accumulators[property].push(results)
|
969
|
+
accumulators.associations = accumulators.associations.concat(results.associations)
|
970
|
+
looper(queries)
|
971
|
+
} catch(e) {
|
972
|
+
const error = { errors: [e], query: query.query };
|
973
|
+
config.config.skipSemantics = null
|
974
|
+
errorHandler(error)
|
975
|
+
}
|
976
|
+
} else {
|
977
|
+
// extra config is def from a time like operators or bridges or words etc
|
978
|
+
// it will just get added to the config
|
979
|
+
const extraConfig = queryOrExtraConfig
|
980
|
+
console.log('config', extraConfig)
|
981
|
+
accumulators[property].push({ extraConfig: true, ...extraConfig })
|
982
|
+
looper(queries)
|
983
|
+
}
|
984
|
+
}
|
985
|
+
|
986
|
+
const finish = () => {
|
987
|
+
const instanceName = `${target}.instance.json`
|
988
|
+
console.log(`Writing instance file ${instanceName}`)
|
989
|
+
const stabilizeAssociations = (associations) => {
|
990
|
+
for (let association of associations) {
|
991
|
+
association.sort()
|
992
|
+
}
|
993
|
+
associations.sort()
|
994
|
+
};
|
995
|
+
const stabilizeOutput = (template) => {
|
996
|
+
stabilizeAssociations(template.associations)
|
997
|
+
const stabilize = (results) => {
|
998
|
+
for (let result of results) {
|
999
|
+
if (result.extraConfig) {
|
1000
|
+
} else {
|
1001
|
+
delete result.load_cache_time
|
1002
|
+
delete result.times
|
1003
|
+
delete result.memory_free_percent
|
1004
|
+
result.hierarchy.sort()
|
1005
|
+
stabilizeAssociations(result.associations)
|
1006
|
+
}
|
1007
|
+
}
|
1008
|
+
}
|
1009
|
+
stabilize(template.resultss)
|
1010
|
+
stabilize(template.fragments)
|
1011
|
+
return template
|
1012
|
+
};
|
1013
|
+
stabilizeOutput(accumulators)
|
1014
|
+
fs.writeFileSync(instanceName, JSON.stringify(Object.assign({ queries: template.queries }, accumulators), 0, 2))
|
1015
|
+
|
1016
|
+
// km tests file
|
1017
|
+
const testsName = `./${target}.test.json`
|
1018
|
+
if (!fs.existsSync(testsName)) {
|
1019
|
+
console.log(`Writing km file tests file "${testsName}" since it does not exist`)
|
1020
|
+
fs.writeFileSync(testsName, JSON.stringify({}, 0, 2))
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
const toProperties = (queryStringOrProperties) => {
|
1025
|
+
if (typeof queryStringOrProperties == 'string') {
|
1026
|
+
return { query: queryStringOrProperties}
|
1027
|
+
} else {
|
1028
|
+
return queryStringOrProperties
|
1029
|
+
}
|
1030
|
+
}
|
1031
|
+
let todo = (template.queries || []).map((query) => { return { property: 'resultss', query, skipSemantics: false } })
|
1032
|
+
todo = todo.concat((template.fragments || []).map((query) => { return Object.assign({}, toProperties(query), { property: 'fragments', skipSemantics: false }) }))
|
1033
|
+
todo = todo.concat((template.semantics || []).map((definition) => { return { property: 'semantics', query: `${definition.from}\n${definition.to}`, skipSemantics: true } }))
|
1034
|
+
looper(Object.assign([], todo))
|
1035
|
+
}
|
1036
|
+
|
1037
|
+
const knowledgeModule = async ({
|
1038
|
+
module: moduleFromJSFile,
|
1039
|
+
description,
|
1040
|
+
section,
|
1041
|
+
config,
|
1042
|
+
demo,
|
1043
|
+
test,
|
1044
|
+
template,
|
1045
|
+
errorHandler = defaultErrorHandler,
|
1046
|
+
process: processResults = defaultProcess,
|
1047
|
+
stopAtFirstFailure = true,
|
1048
|
+
beforeQuery = () => {},
|
1049
|
+
beforeTests = () => {},
|
1050
|
+
afterTests = () => {},
|
1051
|
+
beforeTest = () => {},
|
1052
|
+
afterTest = () => {}
|
1053
|
+
} = {}) => {
|
1054
|
+
|
1055
|
+
const isProcess = require.main === moduleFromJSFile
|
1056
|
+
const testConfig = test
|
1057
|
+
|
1058
|
+
// remove test only stuff
|
1059
|
+
if (!isProcess) {
|
1060
|
+
config.config.operators = config.config.operators.filter( (operator) => {
|
1061
|
+
if (operator.development) {
|
1062
|
+
return false
|
1063
|
+
} else {
|
1064
|
+
return true
|
1065
|
+
}
|
1066
|
+
})
|
1067
|
+
config.config.bridges = config.config.bridges.filter( (bridge) => {
|
1068
|
+
if (bridge.development) {
|
1069
|
+
return false
|
1070
|
+
} else {
|
1071
|
+
return true
|
1072
|
+
}
|
1073
|
+
})
|
1074
|
+
}
|
1075
|
+
|
1076
|
+
if (!moduleFromJSFile) {
|
1077
|
+
throw "'module' is a required parameter. The value should be either 'module' or a lambda that will be called when the file is acting as a module."
|
1078
|
+
}
|
1079
|
+
if (!config) {
|
1080
|
+
throw "'config' is a required parameter. The value should the config that defines the knowledge module."
|
1081
|
+
}
|
1082
|
+
if (!config.name) {
|
1083
|
+
throw "config must have 'name' set to the knowledge module name."
|
1084
|
+
}
|
1085
|
+
if (!description) {
|
1086
|
+
throw "'description' is a required parameter. The value should the description of the knowledge module."
|
1087
|
+
}
|
1088
|
+
if (!test) {
|
1089
|
+
throw "'test' is a required parameter. The value should the path to the file used to store the tests of the knowledge module and the contents of the file in the form { name: <filePath>, contexts: <json> }."
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
let module
|
1093
|
+
if (_.isFunction(moduleFromJSFile)) {
|
1094
|
+
module = moduleFromJSFile
|
1095
|
+
} else {
|
1096
|
+
module = () => {
|
1097
|
+
config.rebuild({ isModule: true })
|
1098
|
+
moduleFromJSFile.exports = config
|
1099
|
+
}
|
1100
|
+
}
|
1101
|
+
processResults = processResults({ config, errorHandler })
|
1102
|
+
config.description = description
|
1103
|
+
config.demo = demo
|
1104
|
+
if (typeof test === 'object' && test.contents) {
|
1105
|
+
config.tests = test.contents
|
1106
|
+
test = test.name
|
1107
|
+
} else {
|
1108
|
+
if (fs && fs.existsSync(test)) {
|
1109
|
+
config.tests = JSON.parse(fs.readFileSync(test))
|
1110
|
+
} else {
|
1111
|
+
config.tests = {}
|
1112
|
+
}
|
1113
|
+
}
|
1114
|
+
|
1115
|
+
if (!isProcess) {
|
1116
|
+
if (template) {
|
1117
|
+
if (config.needsRebuild(template.template, template.instance)) {
|
1118
|
+
throw `This module "${config.name}" cannot be used because the instance file needs rebuilding. Run on the command line with no arguements or the -rt argument to rebuild.`
|
1119
|
+
}
|
1120
|
+
config.load(template.template, template.instance)
|
1121
|
+
}
|
1122
|
+
}
|
1123
|
+
|
1124
|
+
if (isProcess) {
|
1125
|
+
// setup();
|
1126
|
+
const parser = new ArgumentParser({
|
1127
|
+
description: 'Entodicton knowledge module'
|
1128
|
+
})
|
1129
|
+
|
1130
|
+
parser.add_argument('-t', '--test', { action: 'store_true', help: 'Run the tests. Create tests by running with the --query + --save flag' })
|
1131
|
+
parser.add_argument('-tv', '--testVerbose', { action: 'store_true', help: 'Run the tests in verbose mode. Create tests by running with the --query or --loop with the --save flag' })
|
1132
|
+
parser.add_argument('-tva', '--testAllVerbose', { action: 'store_true', help: 'Run the tests in verbose mode. All the tests will be run instead of stopping at first failure. Create tests by running with the --query or --loop with the --save flag' })
|
1133
|
+
parser.add_argument('-n', '--count', { help: 'Number of times to run the tests. Default is one. Use this to check for flakey test. If possible the system will print out a message with the word "hint" suggesting how to fix the problem' })
|
1134
|
+
// parser.add_argument('-b', '--build', { help: 'Specify the template file name of the form <kmName>. There should be a file called <baseKmName>.<kmName>.template.json with the queries to run. For example { queries: [...] }. The template file will be run and generate an instantiation called <baseKmName>.<kmName>.instance.json and a file called <kmName>.js that will load the template file (this is file generated only if not already existing) and a test file called <KmName>.tests.json. This can then be loaded into an instance of the current knowledge module to setup initial conditions.' })
|
1135
|
+
parser.add_argument('-rt', '--rebuildTemplate', { action: 'store_true', help: 'Force a template rebuild' })
|
1136
|
+
parser.add_argument('-l', '--loop', { action: 'store_true', help: 'Run a loop so that multiply queries may be run' })
|
1137
|
+
parser.add_argument('-i', '--info', { action: 'store_true', help: 'Print meta-data for the module' })
|
1138
|
+
parser.add_argument('-v', '--vimdiff', { action: 'store_true', help: 'For failures run vimdiff' })
|
1139
|
+
parser.add_argument('-g', '--greg', { action: 'store_true', help: 'Set the server to be localhost so I can debug stuff' })
|
1140
|
+
parser.add_argument('-r', '--retrain', { action: 'store_true', help: 'Get the server to retrain the neural nets' })
|
1141
|
+
parser.add_argument('-q', '--query', { help: 'Run the specified query' })
|
1142
|
+
parser.add_argument('-qd', '--queryDelete', { help: 'Delete the specified query from the tests file' })
|
1143
|
+
parser.add_argument('-c', '--clean', { help: 'Remove data from the test files. a === association' })
|
1144
|
+
parser.add_argument('-od', '--objectDiff', { action: 'store_true', help: 'When showing the objects use a colour diff' })
|
1145
|
+
parser.add_argument('-daa', '--dontAddAssociations', { action: 'store_true', help: 'Do not add associations from the tests.' })
|
1146
|
+
parser.add_argument('-p', '--print', { help: 'Print the specified elements c === config, w === words, b === bridges, o === operators d === objects (d for data), h === hierarchy, g === generators, s === semantics, l === load t=tests ordering p === priorities a == associations j == JSON sent to server. for example --print wb' })
|
1147
|
+
parser.add_argument('-s', '--save', { action: 'store_true', help: 'When running with the --query flag this will save the current run to the test file. When running without the --query flag all tests will be run and resaved.' })
|
1148
|
+
parser.add_argument('-sd', '--saveDeveloper', { action: 'store_true', help: 'Same as -s but the query will not show up in the info command.' })
|
1149
|
+
parser.add_argument('-d', '--debug', { action: 'store_true', help: 'When running with the --debug flag this set the debug flag in the config' })
|
1150
|
+
parser.add_argument('-da', '--debugAssociation', { help: 'When running with the --debugAssociation flag the debugging will break when the specified association is added to the config' })
|
1151
|
+
parser.add_argument('-dh', '--debugHierarchy', { help: 'When running with the --debugHierarchy flag the debugging will break when the specified child-parent pair is added to the config for the main config. Set DEBUG_HIERARCHY to debug any config loaded. For example DEBUG_HIERARCHY=\'["cat", "mammel"]\'' })
|
1152
|
+
parser.add_argument('-db', '--debugBridge', { help: 'When running with the --debugBridge flag the debugging will break when the specified bridge is added to the config for the main config. Set DEBUG_BRIDGE to debug any config loaded. For example DEBUG_BRIDGE=\'id/level\'' })
|
1153
|
+
|
1154
|
+
const args = parser.parse_args()
|
1155
|
+
args.count = args.count || 1
|
1156
|
+
|
1157
|
+
if (args.debugAssociation) {
|
1158
|
+
global.entodictonDebugAssociation = JSON.parse(args.debugAssociation)
|
1159
|
+
}
|
1160
|
+
if (args.debugHierarchy) {
|
1161
|
+
global.entodictonDebugHierarchy = JSON.parse(args.debugHierarchy)
|
1162
|
+
}
|
1163
|
+
if (args.debugBridge) {
|
1164
|
+
// id/level
|
1165
|
+
global.entodictonDebugBridge = args.debugBridge.split('/')
|
1166
|
+
if (global.entodictonDebugBridge.length !== 2) {
|
1167
|
+
console.log('Expected DEBUG_BRIDGE to be of the form "id/level"');
|
1168
|
+
}
|
1169
|
+
}
|
1170
|
+
|
1171
|
+
if (args.clean) {
|
1172
|
+
const tests = JSON.parse(fs.readFileSync(testConfig.name))
|
1173
|
+
for (let test of tests) {
|
1174
|
+
delete test.associations
|
1175
|
+
}
|
1176
|
+
writeTestFile(testConfig.name, tests)
|
1177
|
+
console.log(`Cleaned ${testConfig.name}`)
|
1178
|
+
return
|
1179
|
+
}
|
1180
|
+
|
1181
|
+
if (args.queryDelete) {
|
1182
|
+
let tests = JSON.parse(fs.readFileSync(testConfig.name))
|
1183
|
+
tests = tests.filter( (test) => test.query !== args.queryDelete );
|
1184
|
+
writeTestFile(testConfig.name, tests)
|
1185
|
+
console.log(`Remove the test for "${args.queryDelete}"`)
|
1186
|
+
return
|
1187
|
+
}
|
1188
|
+
|
1189
|
+
const options = { rebuild: false }
|
1190
|
+
if (args.rebuildTemplate) {
|
1191
|
+
options.rebuild = true
|
1192
|
+
}
|
1193
|
+
if (args.greg) {
|
1194
|
+
config.server('http://localhost:3000', '6804954f-e56d-471f-bbb8-08e3c54d9321')
|
1195
|
+
}
|
1196
|
+
|
1197
|
+
if (args.debug) {
|
1198
|
+
config.config.debug = true
|
1199
|
+
}
|
1200
|
+
|
1201
|
+
if (template) {
|
1202
|
+
const needsRebuild = config.needsRebuild(template.template, template.instance, options)
|
1203
|
+
if (needsRebuild) {
|
1204
|
+
console.log(`This module "${config.name}" needs rebuilding all other arguments will be ignored. Try again after the template is rebuilt.`)
|
1205
|
+
|
1206
|
+
}
|
1207
|
+
config.load(template.template, template.instance, { rebuild: needsRebuild })
|
1208
|
+
if (needsRebuild) {
|
1209
|
+
return
|
1210
|
+
}
|
1211
|
+
}
|
1212
|
+
|
1213
|
+
if (!args.save && !args.rebuildTemplate && !args.dontAddAssociations) {
|
1214
|
+
config.addAssociationsFromTests(config.tests);
|
1215
|
+
//for (let query in config.tests) {
|
1216
|
+
// config.addAssociations(config.tests[query].associations || []);
|
1217
|
+
//}
|
1218
|
+
}
|
1219
|
+
|
1220
|
+
/*
|
1221
|
+
if (args.buildTemplate) {
|
1222
|
+
if (template) {
|
1223
|
+
config.rebuild()
|
1224
|
+
config.load(template.template, template.instance, { rebuild: true })
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
*/
|
1228
|
+
|
1229
|
+
if (args.print) {
|
1230
|
+
if (args.print.includes('t')) {
|
1231
|
+
console.log("Test queries")
|
1232
|
+
let counter = 0
|
1233
|
+
for (const test of config.tests) {
|
1234
|
+
console.log(`${counter} - ${test.query}`)
|
1235
|
+
counter += 1
|
1236
|
+
}
|
1237
|
+
}
|
1238
|
+
if (args.print.includes('c')) {
|
1239
|
+
const { data } = setupProcessB({ config })
|
1240
|
+
console.log("Config as sent to server")
|
1241
|
+
console.log(JSON.stringify(data, null, 2));
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
if (args.print.includes('l')) {
|
1245
|
+
console.log('Module load ordering')
|
1246
|
+
for (const km of config.configs) {
|
1247
|
+
console.log(` ${km.name}`)
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
if (args.print.includes('w')) {
|
1251
|
+
for (const word in config.config.words) {
|
1252
|
+
console.log(word.concat(' ', ...config.config.words[word].map((def) => JSON.stringify(def))))
|
1253
|
+
}
|
1254
|
+
}
|
1255
|
+
if (args.print.includes('b')) {
|
1256
|
+
for (const bridge of config.config.bridges) {
|
1257
|
+
console.log(JSON.stringify(bridge))
|
1258
|
+
}
|
1259
|
+
}
|
1260
|
+
if (args.print.includes('o')) {
|
1261
|
+
for (const operator of config.config.operators) {
|
1262
|
+
console.log(JSON.stringify(operator))
|
1263
|
+
}
|
1264
|
+
}
|
1265
|
+
if (args.print.includes('j')) {
|
1266
|
+
const { data } = setupProcessB( { config } )
|
1267
|
+
console.log(JSON.stringify(data, null, 2))
|
1268
|
+
}
|
1269
|
+
if (args.print.includes('a')) {
|
1270
|
+
console.log('associations ================')
|
1271
|
+
const properties = ['negative', 'positive']
|
1272
|
+
for (let property of properties) {
|
1273
|
+
console.log(` ${property} ===============`)
|
1274
|
+
for (let association of config.config.associations[property]) {
|
1275
|
+
console.log(` ${JSON.stringify(association)}`)
|
1276
|
+
}
|
1277
|
+
}
|
1278
|
+
}
|
1279
|
+
if (args.print.includes('d')) {
|
1280
|
+
console.log(JSON.stringify(config.config.objects, null, 2))
|
1281
|
+
}
|
1282
|
+
if (args.print.includes('p')) {
|
1283
|
+
for (let priority of config.config.priorities) {
|
1284
|
+
console.log(JSON.stringify(priority))
|
1285
|
+
}
|
1286
|
+
}
|
1287
|
+
if (args.print.includes('h')) {
|
1288
|
+
for (let edge of config.config.hierarchy) {
|
1289
|
+
console.log(JSON.stringify(edge))
|
1290
|
+
}
|
1291
|
+
}
|
1292
|
+
if (args.print.includes('g')) {
|
1293
|
+
const easyToRead = _.cloneDeep(config.config.generators)
|
1294
|
+
for (const semantic of easyToRead) {
|
1295
|
+
semantic.match = semantic.match.toString()
|
1296
|
+
semantic.apply = semantic.apply.toString()
|
1297
|
+
}
|
1298
|
+
console.dir(easyToRead)
|
1299
|
+
}
|
1300
|
+
if (args.print.includes('s')) {
|
1301
|
+
const easyToRead = _.cloneDeep(config.config.semantics)
|
1302
|
+
for (const semantic of easyToRead) {
|
1303
|
+
semantic.match = semantic.match.toString()
|
1304
|
+
semantic.apply = semantic.apply.toString()
|
1305
|
+
}
|
1306
|
+
console.dir(easyToRead)
|
1307
|
+
}
|
1308
|
+
}
|
1309
|
+
|
1310
|
+
if (args.retrain) {
|
1311
|
+
config.config.retrain = true
|
1312
|
+
}
|
1313
|
+
|
1314
|
+
if (args.test || args.testVerbose || args.testAllVerbose || args.save) {
|
1315
|
+
global.transitoryMode = true
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
if (!args.query && !args.test && !args.info && (args.save || args.saveDeveloper)) {
|
1319
|
+
global.transitoryMode = true
|
1320
|
+
saveTests(config, test, beforeQuery, testConfig, args.saveDeveloper)
|
1321
|
+
// } else if (args.build) {
|
1322
|
+
// build({ config, target: args.build, beforeQuery, errorHandler })
|
1323
|
+
} else if (args.info) {
|
1324
|
+
showInfo(description, section, config)
|
1325
|
+
} else if (args.test || args.testVerbose || args.testAllVerbose) {
|
1326
|
+
// TODO make test always a string
|
1327
|
+
if (typeof test === 'string') {
|
1328
|
+
const l = (n) => {
|
1329
|
+
if (n === 0) {
|
1330
|
+
return
|
1331
|
+
}
|
1332
|
+
runTests(config, test, { debug: args.debug, testConfig: testConfig, verbose: args.testVerbose || args.testAllVerbose, stopAtFirstError: !args.testAllVerbose, beforeQuery, beforeTests, afterTests, beforeTest, afterTest }).then((results) => {
|
1333
|
+
if (results.length > 0 && args.vimdiff) {
|
1334
|
+
for (const result of results) {
|
1335
|
+
vimdiff(result.expected, result.actual)
|
1336
|
+
}
|
1337
|
+
}
|
1338
|
+
if (results.length > 0) {
|
1339
|
+
let headerShown = false
|
1340
|
+
console.log('**************************** ERRORS ************************')
|
1341
|
+
for (const result of results) {
|
1342
|
+
console.log('Utterance: ', result.utterance)
|
1343
|
+
if (JSON.stringify(result.expected.paraphrases) !== JSON.stringify(result.actual.paraphrases)) {
|
1344
|
+
if (!headerShown) {
|
1345
|
+
console.log(' Failure')
|
1346
|
+
}
|
1347
|
+
console.log(' expected paraphrases', result.expected.paraphrases)
|
1348
|
+
console.log(' actual paraphrases ', result.actual.paraphrases)
|
1349
|
+
|
1350
|
+
headerShown = true
|
1351
|
+
}
|
1352
|
+
if (JSON.stringify(result.expected.responses) !== JSON.stringify(result.actual.responses)) {
|
1353
|
+
if (!headerShown) {
|
1354
|
+
console.log(' Failure')
|
1355
|
+
}
|
1356
|
+
console.log(' expected responses ', result.expected.responses)
|
1357
|
+
console.log(' actual responses ', result.actual.responses)
|
1358
|
+
headerShown = true
|
1359
|
+
}
|
1360
|
+
}
|
1361
|
+
if (!headerShown) {
|
1362
|
+
console.log('There are failures due to things other than paraphrases and response being different. They are not shown because you ran -tv or -tva which only shows difference in paraphrase and results. Usually what I do is -s and do a diff to make sure there are no other problems. If the paraphrases or results were different they would have shown here.')
|
1363
|
+
}
|
1364
|
+
console.log('use -v arg to write files expected.json and actual.json in the current directory for detailed comparison. Or do -s and then git diff the changes.')
|
1365
|
+
// console.log(JSON.stringify(contexts))
|
1366
|
+
console.log('**************************** ERRORS ************************')
|
1367
|
+
}
|
1368
|
+
// const contexts = { failures: results }
|
1369
|
+
l(n - 1)
|
1370
|
+
}).catch((error) => {
|
1371
|
+
errorHandler(error)
|
1372
|
+
})
|
1373
|
+
}
|
1374
|
+
l(args.count)
|
1375
|
+
} else {
|
1376
|
+
test()
|
1377
|
+
}
|
1378
|
+
} else if (args.loop) {
|
1379
|
+
const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout })
|
1380
|
+
const f = () => readline.question('Enter query? (newline to quit) ', query => {
|
1381
|
+
query = query.trim()
|
1382
|
+
if (query.length === 0) {
|
1383
|
+
return readline.close()
|
1384
|
+
}
|
1385
|
+
// const promise = processResults(_process(config, query, { testsFN: test }))
|
1386
|
+
const promise = _process(config, query, { testsFN: test }).then((results) => {
|
1387
|
+
console.log(results.responses.join(' '))
|
1388
|
+
})
|
1389
|
+
if (!('then' in promise)) {
|
1390
|
+
throw 'Return a promise from process in the definition of knowledgeModule'
|
1391
|
+
}
|
1392
|
+
promise.then(() => f()).catch( (e) => f() )
|
1393
|
+
})
|
1394
|
+
f()
|
1395
|
+
} else if (args.query) {
|
1396
|
+
const objects = getObjects(config.config.objects)(config.uuid)
|
1397
|
+
// for the compare
|
1398
|
+
if (args.objectDiff) {
|
1399
|
+
global.beforeObjects = _.cloneDeep(objects)
|
1400
|
+
}
|
1401
|
+
beforeQuery({ query: args.query, isModule: false, objects })
|
1402
|
+
try {
|
1403
|
+
processResults(_process(config, args.query, { dontAddAssociations: args.dontAddAssociations, writeTests: args.save || args.saveDeveloper, saveDeveloper: args.saveDeveloper, testConfig, testsFN: test, beforeQuery }))
|
1404
|
+
} catch( error ) {
|
1405
|
+
console.log('Error', error);
|
1406
|
+
}
|
1407
|
+
}
|
1408
|
+
} else {
|
1409
|
+
config.addAssociationsFromTests(config.tests);
|
1410
|
+
//for (let query in config.tests) {
|
1411
|
+
// config.addAssociations(config.tests[query].associations || []);
|
1412
|
+
//}
|
1413
|
+
module()
|
1414
|
+
}
|
1415
|
+
}
|
1416
|
+
|
1417
|
+
/*
|
1418
|
+
const test = (name) => {
|
1419
|
+
return {
|
1420
|
+
test: test,
|
1421
|
+
contents: require(name)
|
1422
|
+
}
|
1423
|
+
}
|
1424
|
+
*/
|
1425
|
+
|
1426
|
+
const ensureTestFile = (module, name, type) => {
|
1427
|
+
const isProcess = require.main === module
|
1428
|
+
if (isProcess) {
|
1429
|
+
const fn = `./${name}.${type}.json`
|
1430
|
+
if (!fs.existsSync(fn)) {
|
1431
|
+
console.log('writing')
|
1432
|
+
fs.writeFileSync(fn, '[]')
|
1433
|
+
}
|
1434
|
+
}
|
1435
|
+
}
|
1436
|
+
|
1437
|
+
function where(goUp = 2) {
|
1438
|
+
const e = new Error();
|
1439
|
+
const regexForm1 = /\((.*):(\d+):(\d+)\)$/
|
1440
|
+
const regexForm2 = /at (.*):(\d+):(\d+)$/
|
1441
|
+
const line = e.stack.split("\n")[goUp];
|
1442
|
+
const match = regexForm1.exec(line) || regexForm2.exec(line);
|
1443
|
+
return `${match[1]}:${match[2]}`
|
1444
|
+
}
|
1445
|
+
|
1446
|
+
function w(func) {
|
1447
|
+
func.where = where(3)
|
1448
|
+
return func
|
1449
|
+
}
|
1450
|
+
|
1451
|
+
module.exports = {
|
1452
|
+
process: _process,
|
1453
|
+
where,
|
1454
|
+
w,
|
1455
|
+
submitBug,
|
1456
|
+
ensureTestFile,
|
1457
|
+
build,
|
1458
|
+
processContext,
|
1459
|
+
processContexts,
|
1460
|
+
runTests,
|
1461
|
+
knowledgeModule,
|
1462
|
+
Semantics,
|
1463
|
+
Semantic,
|
1464
|
+
Generators,
|
1465
|
+
Generator,
|
1466
|
+
Digraph,
|
1467
|
+
analyzeMetaData,
|
1468
|
+
processContextsB,
|
1469
|
+
processInstance,
|
1470
|
+
gs,
|
1471
|
+
flattens,
|
1472
|
+
writeTest
|
1473
|
+
}
|