linny-r 1.3.4 → 1.4.1
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/package.json +1 -1
- package/server.js +48 -6
- package/static/index.html +14 -12
- package/static/linny-r.css +28 -2
- package/static/scripts/linny-r-ctrl.js +51 -8
- package/static/scripts/linny-r-gui.js +150 -75
- package/static/scripts/linny-r-model.js +467 -154
- package/static/scripts/linny-r-utils.js +141 -12
- package/static/scripts/linny-r-vm.js +931 -435
package/package.json
CHANGED
package/server.js
CHANGED
@@ -910,12 +910,12 @@ function loadData(res, url) {
|
|
910
910
|
// the call-back Python script specified for the channel
|
911
911
|
|
912
912
|
function receiver(res, sp) {
|
913
|
-
//This function processes all receiver actions
|
913
|
+
// This function processes all receiver actions.
|
914
914
|
let
|
915
915
|
rpath = anyOSpath(sp.get('path') || ''),
|
916
916
|
rfile = anyOSpath(sp.get('file') || '');
|
917
|
-
// Assume that path is relative to
|
918
|
-
// a (back)slash or
|
917
|
+
// Assume that path is relative to working directory unless it starts
|
918
|
+
// with a (back)slash or specifies drive or volume.
|
919
919
|
if(!(rpath.startsWith(path.sep) || rpath.indexOf(':') >= 0 ||
|
920
920
|
rpath.startsWith(WORKING_DIRECTORY))) {
|
921
921
|
rpath = path.join(WORKING_DIRECTORY, rpath);
|
@@ -1038,8 +1038,49 @@ function rcvrAbort(res, rpath, rfile, log) {
|
|
1038
1038
|
}
|
1039
1039
|
|
1040
1040
|
function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
1041
|
+
// Purge reports older than 24 hours.
|
1041
1042
|
try {
|
1042
|
-
|
1043
|
+
const
|
1044
|
+
now = new Date(),
|
1045
|
+
flist = fs.readdirSync(WORKSPACE.reports);
|
1046
|
+
let n = 0;
|
1047
|
+
for(let i = 0; i < flist.length; i++) {
|
1048
|
+
const
|
1049
|
+
pp = path.parse(flist[i]),
|
1050
|
+
fp = path.join(WORKSPACE.reports, flist[i]);
|
1051
|
+
// NOTE: Only consider text files (extension .txt)
|
1052
|
+
if(pp.ext === '.txt') {
|
1053
|
+
// Delete only if file is older than 24 hours.
|
1054
|
+
const fstat = fs.statSync(fp);
|
1055
|
+
if(now - fstat.mtimeMs > 24 * 3600000) {
|
1056
|
+
// Delete text file
|
1057
|
+
try {
|
1058
|
+
fs.unlinkSync(fp);
|
1059
|
+
n++;
|
1060
|
+
} catch(err) {
|
1061
|
+
console.log('WARNING: Failed to delete', fp);
|
1062
|
+
console.log(err);
|
1063
|
+
}
|
1064
|
+
}
|
1065
|
+
}
|
1066
|
+
}
|
1067
|
+
if(n) console.log(n + 'report file' + (n > 1 ? 's' : '') + 'purged');
|
1068
|
+
} catch(err) {
|
1069
|
+
// Log error, but do not abort.
|
1070
|
+
console.log(err);
|
1071
|
+
}
|
1072
|
+
// Now save the reports.
|
1073
|
+
// NOTE: The optional @ indicates where the run number must be inserted.
|
1074
|
+
// If not specified, append run number to the base report file name.
|
1075
|
+
if(rfile.indexOf('@') < 0) {
|
1076
|
+
rfile += run;
|
1077
|
+
} else {
|
1078
|
+
rfile = rfile.replace('@', run);
|
1079
|
+
}
|
1080
|
+
const base = path.join(rpath, rfile);
|
1081
|
+
let fp;
|
1082
|
+
try {
|
1083
|
+
fp = path.join(base + '-data.txt');
|
1043
1084
|
fs.writeFileSync(fp, data);
|
1044
1085
|
} catch(err) {
|
1045
1086
|
console.log(err);
|
@@ -1048,7 +1089,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
|
1048
1089
|
return;
|
1049
1090
|
}
|
1050
1091
|
try {
|
1051
|
-
fp = path.join(
|
1092
|
+
fp = path.join(base + '-stats.txt');
|
1052
1093
|
fs.writeFileSync(fp, stats);
|
1053
1094
|
} catch(err) {
|
1054
1095
|
console.log(err);
|
@@ -1057,7 +1098,7 @@ function rcvrReport(res, rpath, rfile, run, data, stats, log) {
|
|
1057
1098
|
return;
|
1058
1099
|
}
|
1059
1100
|
try {
|
1060
|
-
fp = path.join(
|
1101
|
+
fp = path.join(base + '-log.txt');
|
1061
1102
|
fs.writeFileSync(fp, log);
|
1062
1103
|
} catch(err) {
|
1063
1104
|
console.log(err);
|
@@ -1630,6 +1671,7 @@ function createWorkspace() {
|
|
1630
1671
|
data: path.join(SETTINGS.user_dir, 'data'),
|
1631
1672
|
diagrams: path.join(SETTINGS.user_dir, 'diagrams'),
|
1632
1673
|
modules: path.join(SETTINGS.user_dir, 'modules'),
|
1674
|
+
reports: path.join(SETTINGS.user_dir, 'reports'),
|
1633
1675
|
solver_output: path.join(SETTINGS.user_dir, 'solver')
|
1634
1676
|
};
|
1635
1677
|
// Create these sub-directories if not aready there
|
package/static/index.html
CHANGED
@@ -165,7 +165,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
165
165
|
// Inform user that newer version exists
|
166
166
|
UI.check_update_modal.element('msg').innerHTML = [
|
167
167
|
'<a href="', GITHUB_REPOSITORY,
|
168
|
-
'/wiki/Linny-R-version-history
|
168
|
+
'/wiki/Linny-R-version-history#version-',
|
169
169
|
info[1].replaceAll('.', ''), '" ',
|
170
170
|
'title="Click to view version release notes" ',
|
171
171
|
'target="_blank">Version <strong>',
|
@@ -354,7 +354,7 @@ and move the cursor over the status bar">
|
|
354
354
|
<img id="clone-btn" class="btn disab sep" src="images/clone.png"
|
355
355
|
title="Copy selection (Ctrl-C) – Alt-click to clone (Alt-C)">
|
356
356
|
<img id="paste-btn" class="btn disab sep" src="images/paste.png"
|
357
|
-
title="Paste selection (Ctrl-V)
|
357
|
+
title="Paste selection (Ctrl-V)">
|
358
358
|
<img id="delete-btn" class="btn disab sep" src="images/delete.png"
|
359
359
|
title="Delete">
|
360
360
|
<img id="undo-btn" class="btn enab" src="images/undo.png"
|
@@ -601,6 +601,14 @@ and move the cursor over the status bar">
|
|
601
601
|
</td>
|
602
602
|
<td style="padding-bottom:4px">Infer and display cost prices</td>
|
603
603
|
</tr>
|
604
|
+
<tr title="Reports will be saved in user/reports/, and removed after 24 h">
|
605
|
+
<td style="padding:0px">
|
606
|
+
<div id="settings-report-results" class="box clear"></div>
|
607
|
+
</td>
|
608
|
+
<td style="padding-bottom:4px">
|
609
|
+
Report results after each run
|
610
|
+
</td>
|
611
|
+
</tr>
|
604
612
|
<tr>
|
605
613
|
<td style="padding:0px">
|
606
614
|
<div id="settings-block-arrows" class="box clear"></div>
|
@@ -1098,7 +1106,7 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1098
1106
|
<div id="note-modal" class="modal">
|
1099
1107
|
<div id="note-dlg" class="inp-dlg">
|
1100
1108
|
<div class="dlg-title">
|
1101
|
-
<span id="note-action">Add</span> note
|
1109
|
+
<span id="note-action">Add</span> note <span id="note-number"></span>
|
1102
1110
|
<img class="cancel-btn" src="images/cancel.png">
|
1103
1111
|
<img class="ok-btn" src="images/ok.png">
|
1104
1112
|
</div>
|
@@ -1280,6 +1288,9 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
1280
1288
|
<div id="process-dlg" class="inp-dlg">
|
1281
1289
|
<div class="dlg-title">
|
1282
1290
|
Process properties
|
1291
|
+
<div class="simbtn">
|
1292
|
+
<img id="process-sim-btn" class="btn sim enab" src="images/process.png">
|
1293
|
+
</div>
|
1283
1294
|
<img class="cancel-btn" src="images/cancel.png">
|
1284
1295
|
<img class="ok-btn" src="images/ok.png">
|
1285
1296
|
</div>
|
@@ -2849,15 +2860,6 @@ where X can be one or several of these letters: ABCDELPQ">
|
|
2849
2860
|
Auto-increment tail number
|
2850
2861
|
</div>
|
2851
2862
|
</div>
|
2852
|
-
<div class="paste-option">
|
2853
|
-
<div id="paste-near-box" class="box checked"></div>
|
2854
|
-
<div class="paste-tactic">
|
2855
|
-
Link to nearest eligible node
|
2856
|
-
</div>
|
2857
|
-
</div>
|
2858
|
-
<div style="font-weight: bold; margin:4px 2px 2px 2px">
|
2859
|
-
Mapping of nodes to link from/to:
|
2860
|
-
</div>
|
2861
2863
|
<div id="paste-scroll-area">
|
2862
2864
|
</div>
|
2863
2865
|
</div>
|
package/static/linny-r.css
CHANGED
@@ -346,6 +346,20 @@ div.contbtn {
|
|
346
346
|
cursor: pointer;
|
347
347
|
}
|
348
348
|
|
349
|
+
div.simbtn {
|
350
|
+
display: none;
|
351
|
+
margin-left: 5px;
|
352
|
+
}
|
353
|
+
|
354
|
+
img.sim {
|
355
|
+
height: 14px;
|
356
|
+
width: 14px;
|
357
|
+
}
|
358
|
+
|
359
|
+
img.sim.enab:hover {
|
360
|
+
background-color: #9e96e5;
|
361
|
+
}
|
362
|
+
|
349
363
|
input {
|
350
364
|
vertical-align: baseline;
|
351
365
|
}
|
@@ -508,6 +522,12 @@ div.notification-msg.first-msg {
|
|
508
522
|
color: black;
|
509
523
|
}
|
510
524
|
|
525
|
+
span.node-details {
|
526
|
+
font-style: italic;
|
527
|
+
color: Gray;
|
528
|
+
margin-left: 15px;
|
529
|
+
}
|
530
|
+
|
511
531
|
#issue-panel {
|
512
532
|
display: none;
|
513
533
|
background-color: Yellow;
|
@@ -2024,6 +2044,7 @@ div.io-box {
|
|
2024
2044
|
bottom: 2px;
|
2025
2045
|
left: 2px;
|
2026
2046
|
width: 40%;
|
2047
|
+
height: 59px;
|
2027
2048
|
}
|
2028
2049
|
|
2029
2050
|
#dataset-outcome {
|
@@ -2241,8 +2262,13 @@ td.equation-selector {
|
|
2241
2262
|
}
|
2242
2263
|
|
2243
2264
|
td.wildcard {
|
2265
|
+
color: #400080;
|
2266
|
+
}
|
2267
|
+
|
2268
|
+
span.wildcard {
|
2244
2269
|
font-weight: bold;
|
2245
|
-
|
2270
|
+
font-style: normal;
|
2271
|
+
color: #a00040;
|
2246
2272
|
}
|
2247
2273
|
|
2248
2274
|
td.dataset-expression,
|
@@ -4670,7 +4696,7 @@ div.docu-sym:hover {
|
|
4670
4696
|
|
4671
4697
|
/* the CALL STACK modal displays the VM call stack */
|
4672
4698
|
#call-stack-dlg {
|
4673
|
-
width:
|
4699
|
+
width: 320px;
|
4674
4700
|
height: 250px;
|
4675
4701
|
}
|
4676
4702
|
|
@@ -36,6 +36,7 @@ SOFTWARE.
|
|
36
36
|
class Controller {
|
37
37
|
constructor() {
|
38
38
|
this.console = true;
|
39
|
+
this.browser_name = '';
|
39
40
|
// Initialize *graphical* controller elements as non-existent
|
40
41
|
this.paper = null;
|
41
42
|
this.buttons = {};
|
@@ -68,7 +69,8 @@ class Controller {
|
|
68
69
|
this.ERROR = {
|
69
70
|
CREATE_FAILED: 'ERROR: failed to create a new SVG element',
|
70
71
|
APPEND_FAILED: 'ERROR: failed to append SVG element to DOM',
|
71
|
-
NO_DATASET_DOT: '"." only makes sense in dataset modifier expressions'
|
72
|
+
NO_DATASET_DOT: '"." only makes sense in dataset modifier expressions',
|
73
|
+
NO_NUMBER_CONTEXT: 'Number # is undefined in this context'
|
72
74
|
};
|
73
75
|
this.WARNING = {
|
74
76
|
NO_CONNECTION: 'No connection with server',
|
@@ -291,8 +293,8 @@ class Controller {
|
|
291
293
|
// Returns TRUE if `name` is a valid Linny-R entity name. These names
|
292
294
|
// must not be empty strings, may not contain brackets, backslashes or
|
293
295
|
// vertical bars, may not end with a colon, and must start with an
|
294
|
-
// underscore, a letter or a digit.
|
295
|
-
//
|
296
|
+
// underscore, a letter or a digit.
|
297
|
+
// These rules are enforced to avoid parsing issues with variable names.
|
296
298
|
// NOTE: normalize to also accept letters with accents
|
297
299
|
if(name === this.TOP_CLUSTER_NAME) return true;
|
298
300
|
name = name.normalize('NFKD').trim();
|
@@ -316,6 +318,14 @@ class Controller {
|
|
316
318
|
return pan;
|
317
319
|
}
|
318
320
|
|
321
|
+
completePrefix(name) {
|
322
|
+
// Returns the prefix part (including the final colon plus space),
|
323
|
+
// or the empty string if none.
|
324
|
+
const p = UI.prefixesAndName(name);
|
325
|
+
p[p.length - 1] = '';
|
326
|
+
return p.join(UI.PREFIXER);
|
327
|
+
}
|
328
|
+
|
319
329
|
sharedPrefix(n1, n2) {
|
320
330
|
const
|
321
331
|
pan1 = this.prefixesAndName(n1),
|
@@ -331,15 +341,48 @@ class Controller {
|
|
331
341
|
return shared.join(this.PREFIXER);
|
332
342
|
}
|
333
343
|
|
344
|
+
colonPrefixedName(name, prefix) {
|
345
|
+
// Replaces a leading colon in `name` by `prefix`.
|
346
|
+
// If `name` identifies a link or a constraint, this is applied to
|
347
|
+
// both node names.
|
348
|
+
const
|
349
|
+
arrow = (name.indexOf(this.LINK_ARROW) >= 0 ?
|
350
|
+
this.LINK_ARROW : this.CONSTRAINT_ARROW),
|
351
|
+
nodes = name.split(arrow);
|
352
|
+
for(let i = 0; i < nodes.length; i++) {
|
353
|
+
nodes[i] = nodes[i].replace(/^:\s*/, prefix);
|
354
|
+
}
|
355
|
+
return nodes.join(arrow);
|
356
|
+
}
|
357
|
+
|
358
|
+
tailNumber(name) {
|
359
|
+
// Returns the string of digits at the end of `name`. If not there,
|
360
|
+
// check prefixes (if any) from right to left for a tail number.
|
361
|
+
const pan = UI.prefixesAndName(name);
|
362
|
+
let n = endsWithDigits(pan.pop());
|
363
|
+
while(!n && pan.length > 0) {
|
364
|
+
n = endsWithDigits(pan.pop());
|
365
|
+
}
|
366
|
+
return n;
|
367
|
+
}
|
368
|
+
|
334
369
|
nameToID(name) {
|
335
370
|
// Returns a name in lower case with link arrow replaced by three
|
336
371
|
// underscores, constraint link arrow by four underscores, and spaces
|
337
372
|
// converted to underscores; in this way, IDs will always be valid
|
338
|
-
// JavaScript object properties
|
339
|
-
// NOTE:
|
340
|
-
//
|
341
|
-
|
342
|
-
|
373
|
+
// JavaScript object properties.
|
374
|
+
// NOTE: Links and constraints are a special case, because their IDs
|
375
|
+
// depend on the *codes* of their nodes.
|
376
|
+
if(name.indexOf(UI.LINK_ARROW) >= 0 ||
|
377
|
+
name.indexOf(UI.CONSTRAINT_ARROW) >= 0) {
|
378
|
+
const obj = MODEL.objectByName(name);
|
379
|
+
if(obj) return obj.identifier;
|
380
|
+
// Empty string signals failure.
|
381
|
+
return '';
|
382
|
+
}
|
383
|
+
// NOTE: replace single quotes by Unicode apostrophe so that they
|
384
|
+
// cannot interfere with JavaScript strings delimited by single quotes.
|
385
|
+
return name.toLowerCase().replace(/\s/g, '_').replace("'", '\u2019');
|
343
386
|
}
|
344
387
|
|
345
388
|
htmlEquationName(n) {
|