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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
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 channel directory unless it starts with
918
- // a (back)slash or specifiess drive or volume
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
- let fp = path.join(rpath, rfile + run + '-data.txt');
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(rpath, rfile + run + '-stats.txt');
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(rpath, rfile + run + '-log.txt');
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#%EF%B8%8F-current-',
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) &ndash; 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) -- WORK IN PROGESS!">
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 &ne; 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 &ne; 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>
@@ -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
- color: #900040;
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: 300px;
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. This is enforced mainly to
295
- // preclude parsing issues with variable names
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: replace single quotes by Unicode apostrophe so that they cannot
340
- // interfere with JavaScript strings delimited by single quotes
341
- return name.replace(this.LINK_ARROW, '___').replace(this.CONSTRAINT_ARROW,
342
- '____').toLowerCase().replace(/\s/g, '_').replace("'", '\u2019');
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) {