bare-script 3.7.6 → 3.8.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/lib/bare.js CHANGED
@@ -8,6 +8,7 @@ import {executeScriptAsync} from './runtimeAsync.js';
8
8
  import {fileURLToPath} from 'url';
9
9
  import {lintScript} from './model.js';
10
10
  import {readFile} from 'fs/promises';
11
+ import {systemGlobalIncludesName} from './library.js';
11
12
  import {urlFileRelative} from './options.js';
12
13
  import {valueBoolean} from './value.js';
13
14
 
@@ -55,12 +56,20 @@ export async function main(options) {
55
56
 
56
57
  // Get the scripts to run
57
58
  let {scripts} = args;
59
+ let ixUserScript = 0;
58
60
  if (args.markdownUp) {
59
61
  scripts = [['code', 'include <markdownUp.bare>'], ...scripts];
62
+ ixUserScript = 1;
63
+
64
+ // Add unittest.bare argument globals
65
+ globals.vUnittestReport = true;
66
+ if (args.static) {
67
+ globals.vUnittestDisabled = true;
68
+ }
60
69
  }
61
70
 
62
71
  // Parse and execute all source files in order
63
- for (const [scriptType, scriptValue] of scripts) {
72
+ for (const [ixScript, [scriptType, scriptValue]] of scripts.entries()) {
64
73
  // Get the script source
65
74
  let scriptName;
66
75
  let scriptSource;
@@ -80,16 +89,58 @@ export async function main(options) {
80
89
  }
81
90
  } else {
82
91
  inlineCount += 1;
83
- scriptName = `<string${inlineCount > 1 ? inlineCount : ''}>`;
92
+ const inlineDisplay = inlineCount - ixUserScript;
93
+ scriptName = `<string${inlineDisplay > 1 ? inlineDisplay : ''}>`;
84
94
  scriptSource = scriptValue;
85
95
  }
86
96
 
87
97
  // Parse the script source
88
98
  const script = parseScript(scriptSource, 1, scriptName);
89
99
 
100
+ // Execute?
101
+ let staticGlobals = null;
102
+ if (args.static !== 's') {
103
+ // Set the globals to use for static analysis (below)
104
+ if (!args.static || ixScript < ixUserScript) {
105
+ staticGlobals = globals;
106
+ } else {
107
+ // Copy global to keep each script as isolated as possible
108
+ staticGlobals = {...globals};
109
+ const globalIncludes = staticGlobals[systemGlobalIncludesName] ?? null;
110
+ if (globalIncludes !== null && typeof globalIncludes === 'object') {
111
+ staticGlobals[systemGlobalIncludesName] = {...globalIncludes};
112
+ }
113
+ }
114
+
115
+ // Execute the script
116
+ const timeBegin = performance.now();
117
+ const {fetchFn} = options;
118
+ const fetchIncludeFn = (fetchURL, fetchOptions) => fetchInclude(fetchFn, fetchURL, fetchOptions);
119
+ const result = await executeScriptAsync(script, {
120
+ 'debug': args.debug ?? false,
121
+ 'fetchFn': fetchIncludeFn,
122
+ 'globals': staticGlobals,
123
+ 'logFn': options.logFn,
124
+ 'systemPrefix': fetchIncludePrefix,
125
+ 'urlFn': scriptType === 'file' ? (url) => urlFileRelative(scriptName, url) : null
126
+
127
+ });
128
+ if (Number.isInteger(result) && result >= 0 && result <= 255) {
129
+ statusCode = result || statusCode;
130
+ } else {
131
+ statusCode = (valueBoolean(result) ? 1 : 0) || statusCode;
132
+ }
133
+
134
+ // Log script execution end with timing
135
+ if (args.debug && ixScript >= ixUserScript) {
136
+ const timeEnd = performance.now();
137
+ options.logFn(`BareScript executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
138
+ }
139
+ }
140
+
90
141
  // Run the bare-script linter?
91
- if (args.static || args.debug) {
92
- const warnings = lintScript(script);
142
+ if (args.static && ixScript >= ixUserScript) {
143
+ const warnings = lintScript(script, staticGlobals);
93
144
  if (warnings.length === 0) {
94
145
  options.logFn(`BareScript static analysis "${scriptName}" ... OK`);
95
146
  } else {
@@ -99,43 +150,12 @@ export async function main(options) {
99
150
  for (const warning of warnings) {
100
151
  options.logFn(warning);
101
152
  }
102
- if (args.static) {
103
- statusCode = 1;
104
- break;
105
- }
153
+ statusCode = 1;
106
154
  }
107
155
  }
108
- if (args.static) {
109
- continue;
110
- }
111
-
112
- // Execute the script
113
- const timeBegin = performance.now();
114
- const {fetchFn} = options;
115
- const fetchIncludeFn = (fetchURL, fetchOptions) => fetchInclude(fetchFn, fetchURL, fetchOptions);
116
- const result = await executeScriptAsync(script, {
117
- 'debug': args.debug ?? false,
118
- 'fetchFn': fetchIncludeFn,
119
- 'globals': globals,
120
- 'logFn': options.logFn,
121
- 'systemPrefix': fetchIncludePrefix,
122
- 'urlFn': scriptType === 'file' ? (url) => urlFileRelative(scriptName, url) : null
123
-
124
- });
125
- if (Number.isInteger(result) && result >= 0 && result <= 255) {
126
- statusCode = result;
127
- } else {
128
- statusCode = valueBoolean(result) ? 1 : 0;
129
- }
130
-
131
- // Log script execution end with timing
132
- if (args.debug) {
133
- const timeEnd = performance.now();
134
- options.logFn(`BareScript executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
135
- }
136
156
 
137
157
  // Stop on error status code
138
- if (statusCode !== 0) {
158
+ if (statusCode !== 0 && !args.static) {
139
159
  break;
140
160
  }
141
161
  }
@@ -202,7 +222,9 @@ export function parseArgs(argv) {
202
222
  } else if (arg === '-m' || arg === '--markdown-up') {
203
223
  args.markdownUp = true;
204
224
  } else if (arg === '-s' || arg === '--static') {
205
- args.static = true;
225
+ args.static = 's';
226
+ } else if (arg === '-x' || arg === '--staticx') {
227
+ args.static = 'x';
206
228
  } else if (arg === '-v' || arg === '--var') {
207
229
  if (iArg + 2 >= argv.length) {
208
230
  throw new Error('argument -v/--var: expected 2 arguments');
@@ -222,7 +244,7 @@ export function parseArgs(argv) {
222
244
 
223
245
  // The command-line interface (CLI) help text
224
246
  export const helpText = `\
225
- usage: bare [-h] [-c CODE] [-d] [-m] [-s] [-v VAR EXPR] [file ...]
247
+ usage: bare [-h] [-c CODE] [-d] [-m] [-s] [-x] [-v VAR EXPR] [file ...]
226
248
 
227
249
  The BareScript command-line interface
228
250
 
@@ -235,4 +257,5 @@ options:
235
257
  -d, --debug enable debug mode
236
258
  -m, --markdown-up run with MarkdownUp stubs
237
259
  -s, --static perform static analysis
260
+ -x, --staticx perform static analysis with execution
238
261
  -v, --var VAR EXPR set a global variable to an expression value`;
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('argsSentinel'):
7
- return
8
- endif
9
- argsSentinel = true
10
-
11
-
12
5
  # The URL arguments model
13
6
  argsTypes = schemaParse( \
14
7
  'group "args.bare"', \
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('dataTableSentinel'):
7
- return
8
- endif
9
- dataTableSentinel = true
10
-
11
-
12
5
  # The data table model's Schema Markdown
13
6
  dataTableTypes = schemaParse( \
14
7
  'group "Data Table"', \
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('diffSentinel'):
7
- return
8
- endif
9
- diffSentinel = true
10
-
11
-
12
5
  # The text line difference model
13
6
  diffTypes = schemaParse( \
14
7
  'group "diff.bare"', \
@@ -104,7 +97,7 @@ function diffLines(left, right):
104
97
  endif
105
98
 
106
99
  # Look ahead to find next matching point
107
- foundMatch = False
100
+ foundMatch = false
108
101
  ixLeftTmp = ixLeft
109
102
  while ixLeftTmp < leftLength:
110
103
  ixRightTmp = ixRight
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('formsSentinel'):
7
- return
8
- endif
9
- formsSentinel = true
10
-
11
-
12
5
  # $function: formsTextElements
13
6
  # $group: forms.bare
14
7
  # $doc: Create a text input [element model](https://github.com/craigahobbs/element-model#readme)
@@ -1,14 +1,6 @@
1
1
  # Licensed under the MIT License
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
-
5
- # Include sentinel
6
- if systemGlobalGet('markdownUpSentinel'):
7
- return
8
- endif
9
- markdownUpSentinel = true
10
-
11
-
12
4
  include <dataTable.bare>
13
5
 
14
6
 
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('pagerSentinel'):
7
- return
8
- endif
9
- pagerSentinel = true
10
-
11
-
12
5
  include <args.bare>
13
6
 
14
7
 
@@ -2,69 +2,60 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('unittestSentinel'):
7
- return
8
- endif
9
- unittestSentinel = true
10
-
11
-
12
5
  include <args.bare>
13
6
  include <dataTable.bare>
14
7
  include <diff.bare>
15
8
 
16
9
 
17
- # Test statistics
18
- unittestTests = {}
19
- unittestWarnings = []
20
-
21
-
22
10
  # $function: unittestRunTest
23
11
  # $group: unittest.bare
24
12
  # $doc: Run a unit test
25
13
  # $arg testName: The test function name
26
14
  async function unittestRunTest(testName):
27
- testFn = unittestRunTestHelper(testName)
28
- if testFn:
29
- testFn()
30
- systemGlobalSet('unittestTestName', null)
15
+ # Tests disabled?
16
+ if systemGlobalGet('vUnittestDisabled'):
17
+ return
31
18
  endif
32
- endfunction
33
19
 
34
-
35
- # For backward compatibility
36
- async function unittestRunTestAsync(testName):
37
- systemLogDebug('unittest.bare: unittestRunTestAsync is deprecated - use unittestRunTest')
38
- return unittestRunTest(testName)
39
- endfunction
40
-
41
-
42
- # unittestRunTest helper function
43
- function unittestRunTestHelper(testName):
44
20
  # Single test argument?
21
+ vUnittestTest = systemGlobalGet('vUnittestTest')
45
22
  if vUnittestTest && vUnittestTest != testName:
46
- return null
23
+ return
47
24
  endif
48
25
 
26
+ # Get the global unittest data
27
+ unittestData = unittestDataGet()
28
+ unittestTests = objectGet(unittestData, 'tests')
29
+ unittestWarnings = objectGet(unittestData, 'warnings')
30
+
49
31
  # Test run multiple times?
50
32
  if objectHas(unittestTests, testName):
51
33
  arrayPush(unittestWarnings, 'Test "' + testName + '" run multiple times')
52
- return null
34
+ return
53
35
  endif
54
36
 
55
37
  # Get the test func
56
38
  testFn = systemGlobalGet(testName)
57
39
  if !testFn:
58
40
  arrayPush(unittestWarnings, 'Test "' + testName + '" not found')
59
- return null
41
+ return
60
42
  endif
61
43
 
62
44
  # Add the unit test result array
63
45
  testFailures = []
64
46
  objectSet(unittestTests, testName, testFailures)
65
- systemGlobalSet('unittestTestName', testName)
66
47
 
67
- return testFn
48
+ # Run the test
49
+ objectSet(unittestData, 'current', testName)
50
+ testFn()
51
+ objectSet(unittestData, 'current', null)
52
+ endfunction
53
+
54
+
55
+ # For backward compatibility
56
+ async function unittestRunTestAsync(testName):
57
+ systemLogDebug('unittest.bare: unittestRunTestAsync is deprecated - use unittestRunTest')
58
+ return unittestRunTest(testName)
68
59
  endfunction
69
60
 
70
61
 
@@ -79,8 +70,13 @@ function unittestEqual(actual, expected, description):
79
70
  return
80
71
  endif
81
72
 
73
+ # Get the global unittest data
74
+ unittestData = unittestDataGet()
75
+ unittestTests = objectGet(unittestData, 'tests')
76
+ unittestCurrent = objectGet(unittestData, 'current')
77
+ testFailures = objectGet(unittestTests, unittestCurrent)
78
+
82
79
  # Add the test failure error lines
83
- testFailures = objectGet(unittestTests, unittestTestName)
84
80
  errorLines = [ \
85
81
  'Equal:', \
86
82
  '', \
@@ -143,8 +139,13 @@ function unittestDeepEqual(actual, expected, description):
143
139
  arrayPush(errorLines, '```')
144
140
  endif
145
141
 
142
+ # Get the global unittest data
143
+ unittestData = unittestDataGet()
144
+ unittestTests = objectGet(unittestData, 'tests')
145
+ unittestCurrent = objectGet(unittestData, 'current')
146
+ testFailures = objectGet(unittestTests, unittestCurrent)
147
+
146
148
  # Add the test failure error lines
147
- testFailures = objectGet(unittestTests, unittestTestName)
148
149
  if description:
149
150
  arrayPush(testFailures, arrayExtend([markdownEscape(description), ''], errorLines))
150
151
  else:
@@ -153,6 +154,17 @@ function unittestDeepEqual(actual, expected, description):
153
154
  endfunction
154
155
 
155
156
 
157
+ # Helper to get the global unittest data object
158
+ function unittestDataGet():
159
+ unittestData = systemGlobalGet('unittestData')
160
+ if unittestData == null:
161
+ unittestData = {'tests': {}, 'warnings': [], 'current': null}
162
+ systemGlobalSet('unittestData', unittestData)
163
+ endif
164
+ return unittestData
165
+ endfunction
166
+
167
+
156
168
  # $function: unittestReport
157
169
  # $group: unittest.bare
158
170
  # $doc: Render the unit test report
@@ -171,10 +183,16 @@ function unittestReport(options):
171
183
 
172
184
  # Parse arguments
173
185
  args = argsParse(unittestReportArguments)
174
- testNameArg = objectGet(args, 'test')
175
- scriptName = objectGet(args, 'script')
186
+ isDisabled = objectGet(args, 'disabled')
176
187
  hideTests = objectGet(args, 'hideTests')
177
188
  isReport = objectGet(args, 'report')
189
+ scriptName = objectGet(args, 'script')
190
+ testNameArg = objectGet(args, 'test')
191
+
192
+ # Disabled?
193
+ if isDisabled:
194
+ return 0
195
+ endif
178
196
 
179
197
  # Script coverage details page?
180
198
  if scriptName:
@@ -182,6 +200,11 @@ function unittestReport(options):
182
200
  return 0
183
201
  endif
184
202
 
203
+ # Get the global unittest data
204
+ unittestData = unittestDataGet()
205
+ unittestTests = objectGet(unittestData, 'tests')
206
+ unittestWarnings = objectGet(unittestData, 'warnings')
207
+
185
208
  # Compute test statistics
186
209
  testNames = arraySort(objectKeys(unittestTests))
187
210
  testCount = arrayLength(testNames)
@@ -318,10 +341,11 @@ endfunction
318
341
 
319
342
  # unittestReport application arguments
320
343
  unittestReportArguments = argsValidate([ \
321
- {'name': 'test', 'global': 'vUnittestTest'}, \
322
- {'name': 'script', 'global': 'vUnittestScript'}, \
344
+ {'name': 'disabled', 'global': 'vUnittestDisabled', 'type': 'bool', 'default': false}, \
323
345
  {'name': 'hideTests', 'global': 'vUnittestHideTests', 'type': 'bool', 'default': false}, \
324
- {'name': 'report', 'global': 'vUnittestReport', 'type': 'bool', 'default': false} \
346
+ {'name': 'report', 'global': 'vUnittestReport', 'type': 'bool', 'default': false}, \
347
+ {'name': 'script', 'global': 'vUnittestScript'}, \
348
+ {'name': 'test', 'global': 'vUnittestTest'} \
325
349
  ])
326
350
 
327
351
 
@@ -2,13 +2,6 @@
2
2
  # https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
3
3
 
4
4
 
5
- # Include sentinel
6
- if systemGlobalGet('unittestMockSentinel'):
7
- return
8
- endif
9
- unittestMockSentinel = true
10
-
11
-
12
5
  # Constants
13
6
  unittestMockPixelsPerPoint = 4 / 3
14
7
  unittestMockDefaultFontSizePx = 12 * unittestMockPixelsPerPoint
package/lib/library.js CHANGED
@@ -82,14 +82,16 @@ const arrayExtendArgs = valueArgsModel([
82
82
  // $group: Array
83
83
  // $doc: Flat an array hierarchy
84
84
  // $arg array: The array to flat
85
+ // $arg depth: The maximum depth of the array hierarchy
85
86
  // $return: The flated array
86
87
  function arrayFlat(args) {
87
- const [array] = valueArgsValidate(arrayFlatArgs, args);
88
- return array.flat(Infinity);
88
+ const [array, depth] = valueArgsValidate(arrayFlatArgs, args);
89
+ return array.flat(depth);
89
90
  }
90
91
 
91
92
  const arrayFlatArgs = valueArgsModel([
92
- {'name': 'array', 'type': 'array'}
93
+ {'name': 'array', 'type': 'array'},
94
+ {'name': 'depth', 'type': 'number', 'integer': true, 'default': 10}
93
95
  ]);
94
96
 
95
97
 
@@ -1987,6 +1989,29 @@ struct SystemFetchRequest
1987
1989
  `);
1988
1990
 
1989
1991
 
1992
+ // System includes object global variable name
1993
+ export const systemGlobalIncludesName = '__bareScriptIncludes';
1994
+
1995
+
1996
+ // $function: systemGlobalIncludesGet
1997
+ // $group: System
1998
+ // $doc: Get the global system includes object
1999
+ // $return: The global system includes object
2000
+ function systemGlobalIncludesGet(unusedArgs, options) {
2001
+ const globals = (options !== null ? (options.globals ?? null) : null);
2002
+ return (globals !== null ? (globals[systemGlobalIncludesName] ?? null) : null);
2003
+ }
2004
+
2005
+
2006
+ // $function: systemGlobalIncludesName
2007
+ // $group: System
2008
+ // $doc: Get the system includes object global variable name
2009
+ // $return: The system includes object global variable name
2010
+ function systemGlobalIncludesNameFn() {
2011
+ return systemGlobalIncludesName;
2012
+ }
2013
+
2014
+
1990
2015
  // $function: systemGlobalGet
1991
2016
  // $group: System
1992
2017
  // $doc: Get a global variable value
@@ -2248,6 +2273,8 @@ export const scriptFunctions = {
2248
2273
  systemCompare,
2249
2274
  systemFetch,
2250
2275
  systemGlobalGet,
2276
+ systemGlobalIncludesGet,
2277
+ 'systemGlobalIncludesName': systemGlobalIncludesNameFn,
2251
2278
  systemGlobalSet,
2252
2279
  systemIs,
2253
2280
  systemLog,
package/lib/model.js CHANGED
@@ -320,9 +320,10 @@ export function validateExpression(expr) {
320
320
  * Lint a BareScript script model
321
321
  *
322
322
  * @param {Object} script - The [BareScript model](./model/#var.vName='BareScript')
323
+ * @param {?Object} globals - The script global variables
323
324
  * @returns {string[]} The array of lint warnings
324
325
  */
325
- export function lintScript(script) {
326
+ export function lintScript(script, globals = null) {
326
327
  const warnings = [];
327
328
  const {statements} = script;
328
329
 
@@ -341,6 +342,15 @@ export function lintScript(script) {
341
342
  }
342
343
  }
343
344
 
345
+ // Unknown global variable?
346
+ if (globals !== null) {
347
+ for (const varName of Object.keys(varUses).sort()) {
348
+ if (!(varName in varAssigns) && !(varName in globals) && !builtinGlobals.has(varName)) {
349
+ lintScriptWarning(warnings, script, statements[varUses[varName]], `Unknown global variable "${varName}"`);
350
+ }
351
+ }
352
+ }
353
+
344
354
  // Iterate global statements
345
355
  const functionsDefined = {};
346
356
  const labelsDefined = {};
@@ -386,6 +396,18 @@ export function lintScript(script) {
386
396
  }
387
397
  }
388
398
 
399
+ // Unknown global variable?
400
+ if (globals !== null) {
401
+ for (const varName of Object.keys(fnVarUses).sort()) {
402
+ if (!(varName in fnVarAssigns) && (args === null || args.indexOf(varName) === -1) &&
403
+ !(varName in globals) && !builtinGlobals.has(varName)) {
404
+ lintScriptWarning(
405
+ warnings, script, fnStatements[fnVarUses[varName]], `Unknown global variable "${varName}"`
406
+ );
407
+ }
408
+ }
409
+ }
410
+
389
411
  // Function argument checks
390
412
  if (args !== null) {
391
413
  const argsDefined = new Set();
@@ -499,6 +521,10 @@ export function lintScript(script) {
499
521
  }
500
522
 
501
523
 
524
+ // Builtin global variable names
525
+ const builtinGlobals = new Set(['false', 'if', 'null', 'true']);
526
+
527
+
502
528
  // Helper to format static analysis warnings
503
529
  function lintScriptWarning(warnings, script, statement, message) {
504
530
  const scriptName = script.scriptName ?? '';
@@ -5,7 +5,7 @@
5
5
 
6
6
  import {BareScriptRuntimeError, evaluateExpression, recordStatementCoverage, scriptFunction} from './runtime.js';
7
7
  import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
8
- import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
8
+ import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions, systemGlobalIncludesName} from './library.js';
9
9
  import {lintScript} from './model.js';
10
10
  import {parseScript} from './parser.js';
11
11
  import {urlFileRelative} from './options.js';
@@ -123,9 +123,9 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
123
123
 
124
124
  // Include?
125
125
  } else if (statementKey === 'include') {
126
- // Fetch the include script text
126
+ // Compute the include script URLs
127
127
  const urlFn = options.urlFn ?? null;
128
- const includeURLs = statement.include.includes.map(({url, system = false}) => {
128
+ const unfilteredIncludeURLs = statement.include.includes.map(({url, system = false}) => {
129
129
  let includeURL;
130
130
  if (system && 'systemPrefix' in options) {
131
131
  includeURL = urlFileRelative(options.systemPrefix, url);
@@ -134,6 +134,21 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
134
134
  }
135
135
  return {includeURL, 'systemInclude': system};
136
136
  });
137
+
138
+ // Filter already included
139
+ let globalIncludes = globals[systemGlobalIncludesName] ?? null;
140
+ if (globalIncludes === null || typeof globalIncludes !== 'object') {
141
+ globalIncludes = {};
142
+ globals[systemGlobalIncludesName] = globalIncludes;
143
+ }
144
+ const includeURLs = unfilteredIncludeURLs.filter(({includeURL}) => {
145
+ if (globalIncludes[includeURL]) {
146
+ return false;
147
+ }
148
+ return true;
149
+ });
150
+
151
+ // Fetch the include script text
137
152
  const responses = await Promise.all(includeURLs.map(async ({includeURL, systemInclude}) => {
138
153
  try {
139
154
  const response = ('fetchFn' in options ? await options.fetchFn(includeURL) : null);
@@ -160,6 +175,12 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
160
175
  throw new BareScriptRuntimeError(script, statement, `Include of "${includeURL}" failed`);
161
176
  }
162
177
 
178
+ // Mark as included. Check again if the URL is included.
179
+ if (globalIncludes[includeURL]) {
180
+ continue;
181
+ }
182
+ globalIncludes[includeURL] = true;
183
+
163
184
  // Parse the include script
164
185
  const includeScript = parseScript(includeText, 1, includeURL);
165
186
  if (systemInclude) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "bare-script",
4
- "version": "3.7.6",
4
+ "version": "3.8.1",
5
5
  "description": "BareScript; a lightweight scripting and expression language",
6
6
  "keywords": [
7
7
  "expression",