fprime-gds 3.5.2a2__py3-none-any.whl → 3.6.1__py3-none-any.whl

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.
@@ -148,13 +148,13 @@ class CmdData(sys_data.SysData):
148
148
  args = []
149
149
  for val, arg_tuple in zip(input_values, self.template.arguments):
150
150
  try:
151
- _, _, arg_type = arg_tuple
151
+ arg_name, _, arg_type = arg_tuple
152
152
  arg_value = arg_type()
153
153
  self.convert_arg_value(val, arg_value)
154
154
  args.append(arg_value)
155
155
  errors.append("")
156
156
  except Exception as exc:
157
- errors.append(str(exc))
157
+ errors.append(f"{arg_name}[{arg_type.__name__}]: {exc}")
158
158
  return args, errors
159
159
 
160
160
  @staticmethod
@@ -49,7 +49,7 @@ class CommandArgumentsInvalidException(werkzeug.exceptions.BadRequest):
49
49
  """Command arguments failed to validate properly"""
50
50
 
51
51
  def __init__(self, errors):
52
- super().__init__("Failed to validate all arguments")
52
+ super().__init__(f"Failed to validate all arguments: {', '.join(errors)}")
53
53
  self.args = errors
54
54
 
55
55
 
@@ -74,7 +74,7 @@ export function command_argument_assignment_helper(argument, squashed_argument_v
74
74
  command_argument_array_serializable_assignment_helper(argument, squashed_argument_value);
75
75
  } else {
76
76
  let is_not_string = typeof(argument.type.MAX_LENGTH) === "undefined";
77
- argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value;
77
+ argument.value = (is_not_string && (squashed_argument_value === FILL_NEEDED)) ? null : squashed_argument_value.toString();
78
78
  }
79
79
  }
80
80
 
@@ -119,7 +119,21 @@ export function squashify_argument(argument) {
119
119
  let field = argument.type.MEMBER_LIST[i][0];
120
120
  value[field] = squashify_argument(argument.value[field]);
121
121
  }
122
- } else if (["U64Type", "U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) != -1) {
122
+ } else if (["U64Type"].indexOf(argument.type.name) !== -1) {
123
+ if (argument.value.startsWith("0x")) {
124
+ // Hexadecimal
125
+ value = BigInt(argument.value, 16);
126
+ } else if (argument.value.startsWith("0b")) {
127
+ // Binary
128
+ value = BigInt(argument.value.slice(2), 2);
129
+ } else if (argument.value.startsWith("0o")) {
130
+ // Octal
131
+ value = BigInt(argument.value.slice(2), 8);
132
+ } else {
133
+ // Decimal
134
+ value = BigInt(argument.value, 10);
135
+ }
136
+ } else if (["U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) !== -1) {
123
137
  if (argument.value.startsWith("0x")) {
124
138
  // Hexadecimal
125
139
  value = parseInt(argument.value, 16);
@@ -134,10 +148,13 @@ export function squashify_argument(argument) {
134
148
  value = parseInt(argument.value, 10);
135
149
  }
136
150
  }
137
- else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) != -1) {
151
+ else if (["I64Type"].indexOf(argument.type.name) !== -1) {
152
+ value = BigInt(argument.value, 10);
153
+ }
154
+ else if (["I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) !== -1) {
138
155
  value = parseInt(argument.value, 10);
139
156
  }
140
- else if (["F64Type", "F32Type"].indexOf(argument.type.name) != -1) {
157
+ else if (["F64Type", "F32Type"].indexOf(argument.type.name) !== -1) {
141
158
  value = parseFloat(argument.value);
142
159
  }
143
160
  else if (argument.type.name == "BoolType") {
@@ -160,22 +177,35 @@ export function squashify_argument(argument) {
160
177
  * @returns: string to display
161
178
  */
162
179
  export function argument_display_string(argument) {
163
- // Base assignment of the value
164
- let string = `${(argument.value == null || argument.value === "") ? FILL_NEEDED: argument.value}`;
165
-
166
- if (argument.type.LENGTH) {
167
- string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
168
- } else if (argument.type.MEMBER_LIST) {
169
- let fields = [];
170
- for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
171
- let field = argument.type.MEMBER_LIST[i][0];
172
- fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
180
+ let string = FILL_NEEDED;
181
+ try {
182
+ // Check for array
183
+ if (argument.type.LENGTH) {
184
+ string = `[${argument.value.map((argument) => argument_display_string(argument)).join(", ")}]`;
185
+ }
186
+ // Serializable
187
+ else if (argument.type.MEMBER_LIST) {
188
+ let fields = [];
189
+ for (let i = 0; i < argument.type.MEMBER_LIST.length; i++) {
190
+ let field = argument.type.MEMBER_LIST[i][0];
191
+ fields.push(`${field}: ${argument_display_string(argument.value[field])}`);
192
+ }
193
+ string = `{${fields.join(", ")}}`
194
+ }
195
+ // String type
196
+ else if (argument.type.MAX_LENGTH) {
197
+ let value = (argument.value == null) ? "" : argument.value;
198
+ value = value.replace(/"/g, '\\\"');
199
+ string = `"${value}"`
200
+ }
201
+ // Unassigned values
202
+ else if (argument.value == null || argument.value === "") {
203
+ string = FILL_NEEDED;
204
+ } else {
205
+ string = squashify_argument(argument);
173
206
  }
174
- string = `{${fields.join(", ")}}`
175
- } else if (argument.type.MAX_LENGTH) {
176
- let value = (argument.value == null) ? "" : argument.value;
177
- value = value.replace(/"/g, '\\\"');
178
- string = `"${value}"`
207
+ } catch (e) {
208
+ string = FILL_NEEDED;
179
209
  }
180
210
  return string;
181
211
  }
@@ -291,7 +321,7 @@ Vue.component("command-scalar-argument", {
291
321
  return ["text", "0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|[1-9]\\d*|0", ""];
292
322
  }
293
323
  else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(this.argument.type.name) != -1) {
294
- return ["number", null, "1"];
324
+ return ["text", "-?[1-9]\\d*|0", ""];
295
325
  }
296
326
  else if (["F64Type", "F32Type"].indexOf(this.argument.type.name) != -1) {
297
327
  return ["number", null, "any"];
@@ -10,7 +10,8 @@ import {command_argument_assignment_helper} from "./arguments.js";
10
10
  import {listExistsAndItemNameNotInList, timeToString} from "../../js/vue-support/utils.js";
11
11
  import {command_history_template} from "./command-history-template.js";
12
12
  import {command_display_string} from "./command-string.js";
13
-
13
+ import { SaferParser } from "../../js/json.js";
14
+ SaferParser.register();
14
15
 
15
16
  /**
16
17
  * command-history:
@@ -103,27 +104,7 @@ Vue.component("command-history", {
103
104
  // Can only set command if it is a child of a command input
104
105
  if (this.$parent.selectCmd) {
105
106
  // command-input expects an array of strings as arguments
106
- this.$parent.selectCmd(cmd.full_name, this.preprocess_args(cmd.args));
107
- }
108
- },
109
- /**
110
- * Process the arguments for a command. If the argument is (or contains) a number, it
111
- * is converted to a string. Other types that should be pre-processed can be added here.
112
- *
113
- * @param {*} args
114
- * @returns args processed for command input (numbers converted to strings)
115
- */
116
- preprocess_args(args) {
117
- if (Array.isArray(args)) {
118
- return args.map(el => this.preprocess_args(el));
119
- } else if (typeof args === 'object' && args !== null) {
120
- return Object.fromEntries(
121
- Object.entries(args).map(([key, value]) => [key, this.preprocess_args(value)])
122
- );
123
- } else if (typeof args === 'number') {
124
- return args.toString();
125
- } else {
126
- return args;
107
+ this.$parent.selectCmd(cmd.full_name, cmd.args);
127
108
  }
128
109
  }
129
110
  }
@@ -15,6 +15,8 @@ import {
15
15
  } from "../../addons/commanding/arguments.js";
16
16
  import {_settings} from "../../js/settings.js";
17
17
  import {command_input_template} from "./command-input-template.js";
18
+ import { SaferParser } from "../../js/json.js";
19
+ SaferParser.register();
18
20
 
19
21
  /**
20
22
  * This helper will help assign command and values in a safe manner by searching the command store, finding a reference,
@@ -38,6 +40,7 @@ function command_assignment_helper(desired_command_name, desired_command_args, p
38
40
  return null;
39
41
  }
40
42
  let selected = _datastore.commands[command_name];
43
+
41
44
  // Set arguments here
42
45
  for (let i = 0; i < selected.args.length; i++) {
43
46
  let assign_value = (desired_command_args.length > i)? desired_command_args[i] : null;
@@ -147,6 +150,7 @@ Vue.component("command-input", {
147
150
  * command reaches the ground system.
148
151
  */
149
152
  sendCommand() {
153
+
150
154
  // Validate the command before sending anything
151
155
  if (!this.validate()) {
152
156
  return;
@@ -158,8 +162,9 @@ Vue.component("command-input", {
158
162
  let _self = this;
159
163
  _self.active = true;
160
164
  let command = this.selected;
165
+ let squashed_args = command.args.map(serialize_arg);
161
166
  this.loader.load("/commands/" + command.full_name, "PUT",
162
- {"key":0xfeedcafe, "arguments": command.args.map(serialize_arg)})
167
+ {"key":0xfeedcafe, "arguments": squashed_args})
163
168
  .then(function() {
164
169
  _self.active = false;
165
170
  // Clear errors, as there is not a problem further
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Contains the templates used to render the command string input box.
5
5
  */
6
- export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...]";
6
+ export let COMMAND_FORMAT_SPEC = "FULL_COMMAND_NAME[[[, ARG1], ARG2], ...] " +
7
+ "where ARGN is a decimal number, quoted string, or an enumerated constant";
7
8
 
8
9
  export let command_string_template = `
9
10
  <div class="fp-flex-repeater">
@@ -10,6 +10,8 @@ import {
10
10
  command_string_template
11
11
  } from "./command-string-template.js";
12
12
  import {argument_display_string, FILL_NEEDED} from "./arguments.js"
13
+ import {SaferParser} from "../../js/json.js";
14
+ SaferParser.register();
13
15
 
14
16
  let STRING_PREPROCESSOR = new RegExp(`(?:"((?:[^\"]|\\\")*)")|([a-zA-Z_][a-zA-Z_0-9.]*)|(${FILL_NEEDED})`, "g");
15
17
 
@@ -61,7 +63,9 @@ Vue.component("command-text", {
61
63
  } catch (e) {
62
64
  // JSON parsing exceptions
63
65
  if (e instanceof SyntaxError) {
64
- this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}`;
66
+ this.error = `Expected command string of the form: ${COMMAND_FORMAT_SPEC}.`;
67
+ } else {
68
+ throw e;
65
69
  }
66
70
  }
67
71
  }
@@ -16,6 +16,8 @@ import {_datastore} from "../../js/datastore.js";
16
16
  import {basicSetup, EditorState, EditorView, linter} from "./third/code-mirror.es.js"
17
17
  import {sequenceLanguageSupport} from "./autocomplete.js"
18
18
  import {processResponse} from "./lint.js";
19
+ import { SaferParser } from "../../js/json.js";
20
+ SaferParser.register();
19
21
 
20
22
  /**
21
23
  * Sequence sender function used to uplink the sequence and return a promise of how to handle the server's return.
@@ -0,0 +1,340 @@
1
+ /**
2
+ * json.js:
3
+ *
4
+ * Contains specialized JSON parser to handle non-standard JSON values from the JavaScript perspective. These values
5
+ * are legal in Python and scala, but not in JavaScript. This parser will safely handle these values.
6
+ *
7
+ * @author mstarch
8
+ */
9
+
10
+
11
+ /**
12
+ * Lexer for JSON built using JSON
13
+ */
14
+ class RegExLexer {
15
+ static TOKEN_EXPRESSIONS = new Map([
16
+ // String tokens: " then
17
+ // any number of:
18
+ // not a quote or \
19
+ // \ followed by not a quote
20
+ // even number of \
21
+ // odd number of \ then " (escaped quotation)
22
+ // then even number of \ then " (terminating non-escaped ")
23
+ ["STRING", /^"([^"\\]|(\\[^"\\])|((\\\\)*)|(\\(\\\\)*)")*(?!\\(\\\\)*)"/],
24
+ // Floating point tokens
25
+ ["NUMBER", /^-?\d+(\.\d+)?([eE][+\-]?\d+)?/],
26
+ // Infinity token
27
+ ["INFINITY", /^-?Infinity/],
28
+ // Null token
29
+ ["NULL", /^null/],
30
+ // NaN token
31
+ ["NAN", /^NaN/],
32
+ // boolean token
33
+ ["BOOLEAN", /^(true)|^(false)/],
34
+ // Open object token
35
+ ["OPEN_OBJECT", /^\{/],
36
+ // Close object token
37
+ ["CLOSE_OBJECT", /^}/],
38
+ // Field separator token
39
+ ["FIELD_SEPARATOR", /^,/],
40
+ // Open list token
41
+ ["OPEN_ARRAY", /^\[/],
42
+ // Close a list token
43
+ ["CLOSE_ARRAY", /^]/],
44
+ // Key Value Separator
45
+ ["VALUE_SEPARATOR", /^:/],
46
+ // Any amount of whitespace is an implicit token
47
+ ["WHITESPACE", /^\s+/]
48
+ ]);
49
+
50
+ /**
51
+ * Tokenize the input string based on JSON tokens.
52
+ * @param input_string: input string to tokenize
53
+ * @return {*[]}: list of tokens in-order
54
+ */
55
+ static tokenize(original_string) {
56
+ let tokens = [];
57
+ let input_string = original_string;
58
+ let total_length = 0;
59
+ let last_token_type = "--NONE--"
60
+ // Consume the whole string
61
+ while (input_string !== "") {
62
+ let matched_something = false;
63
+ for (let [token_type, token_matcher] of RegExLexer.TOKEN_EXPRESSIONS.entries()) {
64
+ let match = token_matcher.exec(input_string)
65
+
66
+ // Token detected
67
+ if (match != null && match.index == 0 ) {
68
+ matched_something = true;
69
+ let matched = match[0];
70
+ tokens.push([token_type, matched]);
71
+ // Consume the string
72
+ input_string = input_string.substring(matched.length);
73
+ total_length += matched.length;
74
+ last_token_type = token_type;
75
+ break;
76
+ }
77
+ }
78
+ // Check for no token match
79
+ if (!matched_something) {
80
+ let say = "Failed to match valid token: '" + input_string.substring(0, 20);
81
+ say += "' Context: '" + original_string.substring(Math.max(total_length - 20, 0), total_length + 20);
82
+ say += "' Last token's type: " + last_token_type + ".";
83
+ throw SyntaxError(say);
84
+ }
85
+ }
86
+ return tokens;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Helper to determine if value is a string
92
+ * @param value: value to check.
93
+ * @return {boolean}: true if string, false otherwise
94
+ */
95
+ function isString(value) {
96
+ return value instanceof String || typeof value === 'string';
97
+ }
98
+
99
+ /**
100
+ * Helper to determine if value is a function
101
+ * @param value: value to check
102
+ * @return {boolean}: true if function, false otherwise
103
+ */
104
+ function isFunction(value) {
105
+ return value instanceof Function || typeof value == "function";
106
+ }
107
+
108
+ /**
109
+ * Convert a string to a number
110
+ * @param value: value to convert
111
+ * @return {bigint|number}: number to return
112
+ */
113
+ function stringToNumber(value) {
114
+ value = value.trim(); // Should be unnecessary
115
+ // Process floats (containing . e or E)
116
+ if (value.search(/[.eE]/) !== -1) {
117
+ return Number.parseFloat(value);
118
+ }
119
+ let number_value = Number.parseInt(value);
120
+ // When the big and normal numbers match, then return the normal number
121
+ if (value !== number_value.toString()) {
122
+ return BigInt(value);
123
+ }
124
+ return number_value;
125
+ }
126
+
127
+ /**
128
+ * Parser to safely handle potential JSON object from Python. Python can produce some non-standard values (infinities,
129
+ * NaNs, etc.) These values then break on the JS Javascript parser. To localize these faults, they are replaced before
130
+ * processing with strings and then formally set during parsing.
131
+ *
132
+ * This is done by looking for tokens in unquoted text and replacing them with string representations.
133
+ *
134
+ * This parser will handle:
135
+ * - -Infinity
136
+ * - Infinity
137
+ * - NaN
138
+ * - null
139
+ * - BigInt
140
+ */
141
+ export class SaferParser {
142
+ static CONVERSION_KEY = "fprime{replacement";
143
+
144
+ static CONVERSION_MAP = new Map([
145
+ ["INFINITY", (value) => (value[0] === "-") ? -Infinity : Infinity],
146
+ ["NAN", NaN],
147
+ ["NULL", null],
148
+ ["NUMBER", stringToNumber]
149
+ ]);
150
+
151
+ static STRINGIFY_TOKENS = [
152
+ Infinity,
153
+ -Infinity,
154
+ NaN,
155
+ "number",
156
+ "bigint",
157
+ null
158
+ ];
159
+
160
+
161
+ // Store the language variants the first time
162
+ static language_parse = JSON.parse;
163
+ static language_stringify = JSON.stringify;
164
+
165
+ /**
166
+ * @brief safely process F Prime JSON syntax
167
+ *
168
+ * Parse method that will replace JSON.parse. This method pre-processes the string data incoming (to be transformed
169
+ * into JavaScript objects) for detection of entities not expressible in JavaScript's JSON implementation. This will
170
+ * replace those entities with a JSON flag object.
171
+ *
172
+ * Then the data is processed by the JavaScript built-in JSON parser (now done safely). The reviver function will
173
+ * safely revive the flag objects into JavaScript representations of those object.
174
+ *
175
+ * Handles:
176
+ * 1. BigInts
177
+ * 2. Inf/-Inf
178
+ * 3. NaN
179
+ * 4. null
180
+ *
181
+ * @param json_string: JSON string data containing potentially bad values
182
+ * @param reviver: reviver function to be combined with our reviver
183
+ * @return {{}}: Javascript Object representation of data safely represented in JavaScript types
184
+ */
185
+ static parse(json_string, reviver) {
186
+ let converted_data = SaferParser.preprocess(json_string);
187
+ // Set up a composite reviver of the one passed in and ours
188
+ let input_reviver = reviver || ((key, value) => value);
189
+ let full_reviver = (key, value) => input_reviver(key, SaferParser.reviver(key, value));
190
+ try {
191
+ let language_parsed = SaferParser.language_parse(converted_data, full_reviver);
192
+ return language_parsed;
193
+ } catch (e) {
194
+ let message = e.toString();
195
+ const matcher = /line (\d+) column (\d+)/
196
+
197
+ // Process the match
198
+ let snippet = "";
199
+ let match = message.match(matcher);
200
+ if (match != null) {
201
+ let lines = converted_data.split("\n");
202
+ let line = lines[Number.parseInt(match[1]) - 1]
203
+ snippet = line.substring(Number.parseInt(match[2]) - 6, Number.parseInt(match[2]) + 5);
204
+ message += ". Offending snippet: " + snippet;
205
+ throw new SyntaxError(message);
206
+ }
207
+ throw e;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @brief safely write the F Prime JSON syntax
213
+ *
214
+ * Stringify method that will replace JSON.stringify. This method post-processes the string data outgoing from
215
+ * JavaScript's built-in stringify method to replace flag-objects with the correct F Prime representation in
216
+ * JavaScript.
217
+ *
218
+ * This uses the javascript stringify handler method to pre-convert unsupported types into a flag object. This flag
219
+ * object is post-converted into a normal string after JSON.stringify has done its best.
220
+ *
221
+ * Handles:
222
+ * 1. BigInts
223
+ * 2. Inf/-Inf
224
+ * 3. NaN
225
+ * 4. null
226
+ *
227
+ * @param data: data object to stringify
228
+ * @param replacer: replacer Array or Function
229
+ * @param space: space for passing into JSON.stringify
230
+ * @return {{}}: JSON string using JSON support for big-ints Int/-Inf, NaN and null.
231
+ */
232
+ static stringify(data, replacer, space) {
233
+ let full_replacer = (key, value) => {
234
+ // Handle array case for excluded field
235
+ if (Array.isArray(replacer) && replacer.indexOf(key) === -1) {
236
+ return undefined;
237
+ }
238
+ // Run input replacer first
239
+ else if (isFunction(replacer)) {
240
+ value = replacer(key, value);
241
+ }
242
+ // Then run our safe replacer
243
+ let replaced = SaferParser.replaceFromObject(key, value);
244
+ return replaced;
245
+ };
246
+ // Stringify JSON using built-in JSON parser and the special replacer
247
+ let json_string = SaferParser.language_stringify(data, full_replacer, space);
248
+ // Post-process JSON string to rework JSON into the wider specification
249
+ let post_replace = SaferParser.postReplacer(json_string);
250
+ return post_replace
251
+ }
252
+
253
+ /**
254
+ * Get replacement object from a JavaScript type
255
+ * @param _: unused
256
+ * @param value: value to replace
257
+ */
258
+ static replaceFromObject(_, value) {
259
+ for (let i = 0; i < SaferParser.STRINGIFY_TOKENS.length; i++) {
260
+ let replacer_type = SaferParser.STRINGIFY_TOKENS[i];
261
+ let mapper_is_string = isString(replacer_type);
262
+ if ((!mapper_is_string && value === replacer_type) || (mapper_is_string && typeof value === replacer_type)) {
263
+ let replace_object = {};
264
+ replace_object[SaferParser.CONVERSION_KEY] = (value == null) ? "null" : value.toString();
265
+ return replace_object;
266
+ }
267
+ }
268
+ return value;
269
+ }
270
+
271
+ /**
272
+ * Replace JSON notation for fprime-replacement objects with the wider JSON specification
273
+ *
274
+ * Replace {"fprime-replacement: "some value"} with <some value> restoring the full JSON specification for items not
275
+ * supported by JavaScript.
276
+ *
277
+ * @param json_string: JSON string to rework
278
+ * @return reworked JSON string
279
+ */
280
+ static postReplacer(json_string) {
281
+ return json_string.replace(/\{\s*"fprime\{replacement"\s*:\s*"([^"]+)"\s*}/sg, "$1");
282
+ }
283
+
284
+ /**
285
+ * Apply process function to raw json string only for data that is not qu
286
+ * @param json_string: JSON string to preprocess
287
+ * @return {string}
288
+ */
289
+ static preprocess(json_string) {
290
+ const CONVERSION_KEYS = Array.from(SaferParser.CONVERSION_MAP.keys());
291
+ let tokens = RegExLexer.tokenize(json_string);
292
+ let converted_text = tokens.map(
293
+ ([token_type, token_text]) => {
294
+ if (CONVERSION_KEYS.indexOf(token_type) !== -1) {
295
+ let replacement_object = {};
296
+ replacement_object[SaferParser.CONVERSION_KEY] = token_type;
297
+ replacement_object["value"] = token_text;
298
+ return SaferParser.language_stringify(replacement_object)
299
+ }
300
+ return token_text;
301
+ });
302
+ return converted_text.join("");
303
+ }
304
+
305
+ /**
306
+ * Inverse of convert removing string and replacing back invalid JSON tokens.
307
+ * @param key: JSON key
308
+ * @param value: JSON value search for the converted value.
309
+ * @return {*}: reverted value or value
310
+ */
311
+ static reviver(key, value) {
312
+ // Look for fprime-replacement and quickly abort if not there
313
+ let replacement_type = value[SaferParser.CONVERSION_KEY];
314
+ if (typeof replacement_type === "undefined") {
315
+ return value;
316
+ }
317
+ let string_value = value["value"];
318
+ let replacer = SaferParser.CONVERSION_MAP.get(replacement_type);
319
+ return isFunction(replacer) ? replacer(string_value) : replacer;
320
+ }
321
+
322
+ /**
323
+ * @brief force all calls to JSON.parse and JSON.stringify to use the SafeParser
324
+ */
325
+ static register() {
326
+ // Override the singleton
327
+ JSON.parse = SaferParser.parse;
328
+ JSON.stringify = SaferParser.stringify;
329
+ }
330
+
331
+ /**
332
+ * @brief remove the JSON.parse safe override
333
+ */
334
+ static deregister() {
335
+ JSON.parse = SaferParser.language_parse;
336
+ JSON.stringify = SaferParser.language_stringify;
337
+ }
338
+ }
339
+ // Take over all JSON.parse and JSON.stringify calls
340
+ SaferParser.register();
@@ -13,7 +13,8 @@
13
13
  */
14
14
  import {config} from "./config.js";
15
15
  import {_settings} from "./settings.js";
16
- import {_validator} from "./validate.js";
16
+ import {SaferParser} from "./json.js";
17
+ SaferParser.register();
17
18
 
18
19
  /**
19
20
  * Function allowing for the saving of some data to a downloadable file.
@@ -50,118 +51,6 @@ export function loadTextFileInputData(event) {
50
51
  });
51
52
  }
52
53
 
53
- /**
54
- * Parser to safely handle potential JSON object from Python. Python can produce some non-standard values (infinities,
55
- * NaNs, etc.) These values then break on the JS Javascript parser. To localize these faults, they are replaced before
56
- * processing with strings and then formally set during parsing.
57
- *
58
- * This is done by looking for tokens in unquoted text and replacing them with string representations.
59
- *
60
- */
61
- class SaferParser {
62
- /**
63
- * Set up the parser
64
- */
65
- constructor() {
66
- this.STATES = {
67
- UNQUOTED: 0,
68
- QUOTED: 1
69
- };
70
- this.FLAG = "-_-您好"; // Extended character usage make collisions less-likely
71
- this.MAPPINGS = [
72
- ["-Infinity", this.FLAG + "-inf", -Infinity],
73
- ["Infinity", this.FLAG + "inf", Infinity],
74
- ["NaN", this.FLAG + "nan", NaN],
75
- ["null", this.FLAG + "null", null]
76
- ];
77
- this.state = this.STATES.UNQUOTED;
78
- }
79
-
80
- /**
81
- * Parse method that will replace JSON.parse. This handles known bad cases and also prints better error messages
82
- * including the working snippets of text.
83
- * @param rawData: string data
84
- * @return {{}|any}: Javascript Object representation of data.
85
- */
86
- parse(rawData) {
87
- let converted_data = this.convert(rawData);
88
- try {
89
- return JSON.parse(converted_data, this.revert.bind(this));
90
- } catch (e) {
91
- let message = e.toString();
92
- const matcher = /line (\d+) column (\d+)/
93
-
94
- // Process the match
95
- let snippet = "";
96
- let match = message.match(matcher);
97
- if (match != null) {
98
- let lines = converted_data.split("\n");
99
- let line = lines[Number.parseInt(match[1]) - 1]
100
- snippet = line.substring(Number.parseInt(match[2]) - 6, Number.parseInt(match[2]) + 5);
101
- message += ". Offending snippet: " + snippet;
102
- }
103
- _validator.updateErrors([message]);
104
- }
105
- return {};
106
- }
107
-
108
- /**
109
- * Convert data from invalid form to strings.
110
- * @param rawData: raw data including potentially invalid data
111
- * @return {string}: string data in correct JSON format
112
- */
113
- convert(rawData) {
114
- let unprocessed = rawData;
115
- let transformed_data = "";
116
-
117
- while (unprocessed.length > 0) {
118
- let next_quote = unprocessed.indexOf("\"");
119
- let section = (next_quote !== -1) ? unprocessed.substring(0, next_quote + 1) : unprocessed.substring(0);
120
- unprocessed = unprocessed.substring(section.length);
121
- transformed_data += this.processChunk(section);
122
- this.state = (this.state === this.STATES.QUOTED) ? this.STATES.UNQUOTED : this.STATES.QUOTED;
123
- }
124
- return transformed_data;
125
- }
126
-
127
- /**
128
- * Inverse of convert removing string and replacing back invalid JSON tokens.
129
- * @param key: JSON key
130
- * @param value: JSON value search for the converted value.
131
- * @return {*}: reverted value or value
132
- */
133
- revert(key, value) {
134
- for (let i = 0; i < this.MAPPINGS.length; i++) {
135
- if ((this.MAPPINGS[i][1]) === value) {
136
- return this.MAPPINGS[i][2];
137
- }
138
- }
139
- return value;
140
- }
141
-
142
- /**
143
- * Process a section of the JSON string looking for values to convert. This is intended to handle a section of
144
- * quoted or unquoted text but should never handle quoted and unquoted data in one call.
145
- * @param section: section of the data
146
- * @return {*}: converted data
147
- */
148
- processChunk(section) {
149
- // Replaces all the above mappings with a flagged value
150
- let replace_all = (section) => {
151
- for (let i = 0; i < this.MAPPINGS.length; i++) {
152
- section = section.replace(this.MAPPINGS[i][0], "\"" + this.MAPPINGS[i][1] + "\"");
153
- }
154
- return section;
155
- }
156
-
157
- // When out of quoted space,
158
- if (this.state === this.STATES.UNQUOTED) {
159
- return replace_all(section);
160
- }
161
- return section;
162
- }
163
- }
164
-
165
54
  /**
166
55
  * Loader:
167
56
  *
@@ -308,7 +197,7 @@ class Loader {
308
197
  if (this.readyState === 4 && this.status === 200 && raw) {
309
198
  resolve(this.responseText);
310
199
  } else if (this.readyState === 4 && this.status === 200) {
311
- let dataObj = new SaferParser().parse(this.responseText);
200
+ let dataObj = JSON.parse(this.responseText);
312
201
  resolve(dataObj);
313
202
  } else if(this.readyState === 4) {
314
203
  reject(this.responseText);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fprime-gds
3
- Version: 3.5.2a2
3
+ Version: 3.6.1
4
4
  Summary: F Prime Flight Software Ground Data System layer
5
5
  Author-email: Michael Starch <Michael.D.Starch@jpl.nasa.gov>, Thomas Boyer-Chammard <Thomas.Boyer.Chammard@jpl.nasa.gov>
6
6
  License:
@@ -17,7 +17,7 @@ fprime_gds/common/communication/adapters/uart.py,sha256=6SrN42ShVjwNubFg-1YrO09o
17
17
  fprime_gds/common/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  fprime_gds/common/data_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  fprime_gds/common/data_types/ch_data.py,sha256=RP9zSyzNcH0nJ3MYyW_IATnmnHYZ6d0KmoJUJantdBI,6111
20
- fprime_gds/common/data_types/cmd_data.py,sha256=EARx7Q2owmPzq2CZX9oouDYgbCfTnWTZavn2bZlkq-M,7050
20
+ fprime_gds/common/data_types/cmd_data.py,sha256=hsNJHCPKfqDRx4EzZ5DjobyD-oFlcXEWBS8NbDNfqgk,7090
21
21
  fprime_gds/common/data_types/event_data.py,sha256=7_vA6Xwvs9kK1-xJzc6lwO_TtUeWdI7p29B6QJNMc40,5372
22
22
  fprime_gds/common/data_types/exceptions.py,sha256=C16L2lofigH8UmnsYO_fuY6yR20U-ckRcl14HZjQlJc,1054
23
23
  fprime_gds/common/data_types/file_data.py,sha256=4_G9kf4ThC5NzkxnKa0xNYBdi8UDvZg8f5Vw0DdGIBE,3904
@@ -117,7 +117,7 @@ fprime_gds/executables/utils.py,sha256=SbzXRe1p41qMPdifvPap5_4v0T42gZZ_Rs_OYfITd
117
117
  fprime_gds/flask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
118
  fprime_gds/flask/app.py,sha256=kJDCziri_BwZWKUszkR7u3RaNG_FWRzDkdCPsVDAtYM,6720
119
119
  fprime_gds/flask/channels.py,sha256=sOeL-UmWPh2hqYvqj81STpABLlPcjdPgkRwjd3Qx77k,735
120
- fprime_gds/flask/commands.py,sha256=pizE0AQ2Id5xAMBucdAf93ARVinnBQD8y5afAp2i5oo,3636
120
+ fprime_gds/flask/commands.py,sha256=62R3b0mnjc3_XpULpqJyUSvAcfOjAyZNifQ3wqHKO7s,3658
121
121
  fprime_gds/flask/components.py,sha256=a-eG8XJfSrqR8MIzIc9StwbNwxcBqkxYMEYq46S2Bmk,4176
122
122
  fprime_gds/flask/default_settings.py,sha256=SkNfd5R4tv59rcmPiHERIZNIEmzXP3KJcJZektgtZCA,603
123
123
  fprime_gds/flask/errors.py,sha256=yN3jDsJd30jL6aOIF-SqbVoesvReHqPvXlIt8qWB87M,2133
@@ -154,20 +154,20 @@ fprime_gds/flask/static/addons/chart-display/vendor/chartjs-adapter-luxon.min.js
154
154
  fprime_gds/flask/static/addons/chart-display/vendor/hammer.min.js,sha256=SKSRJkZzVMqQzhFeghSWUsCtUon42GUc4gDWk914lDw,20727
155
155
  fprime_gds/flask/static/addons/commanding/addon.js,sha256=d027BtN0LTSIWh1vtL1LIdf0k33tAw1kJ9GIDPbNY4o,263
156
156
  fprime_gds/flask/static/addons/commanding/argument-templates.js,sha256=vx5FP1ZCFT2w_Rc5fIIpaXnOoEq6vhy3tjIbOddwX3w,4528
157
- fprime_gds/flask/static/addons/commanding/arguments.js,sha256=_pcoHumf8qJhw-qfAzZ0_AU1SNAuXSRt55LHYG9t8p4,13304
157
+ fprime_gds/flask/static/addons/commanding/arguments.js,sha256=6UYPwa7KW5nso-pBPs9n9TsGbEq9FUDBtG3eFectThY,14274
158
158
  fprime_gds/flask/static/addons/commanding/command-history-template.js,sha256=2ak2B9eio3PLq6Bnie8iEsQN3HDJYokl0usMMP1D6lE,753
159
- fprime_gds/flask/static/addons/commanding/command-history.js,sha256=iVyMFP_GkqxiMAkK_U2JeTqo96Q-nTghO8eKo3NFXQ0,4816
159
+ fprime_gds/flask/static/addons/commanding/command-history.js,sha256=rEJBsYCFtCXCQewlIUjW2ATG0vPVjQywsbZQ9u8iN0g,3995
160
160
  fprime_gds/flask/static/addons/commanding/command-input-template.js,sha256=Z3fHmPTaAnXDhHMu07bRMBse6wjJSexAStgV9pSeh8Q,2959
161
- fprime_gds/flask/static/addons/commanding/command-input.js,sha256=utcmiLCkzU-CsBvBZsV32_UvKPVTuIOCafHsm3o2104,8973
162
- fprime_gds/flask/static/addons/commanding/command-string-template.js,sha256=7Mq4BPcAS57WoyF9aAMdKLtMFN4DLbQVTAQ8YUQEITI,743
163
- fprime_gds/flask/static/addons/commanding/command-string.js,sha256=q3ThUW45RHDE1gnKPxz44LOJgxXD24YTrYYlvBSfXrA,2553
161
+ fprime_gds/flask/static/addons/commanding/command-input.js,sha256=I2SZslctv7DyOmlmEhVsYRq6tPRAkWuNgikGDOmJn0o,9094
162
+ fprime_gds/flask/static/addons/commanding/command-string-template.js,sha256=tb0NJ24XtiEWaeY6e2MpNnb2uATRi7q7d2TrZEumvqw,825
163
+ fprime_gds/flask/static/addons/commanding/command-string.js,sha256=MAPG9Q0QyWTNxF1Urnxbz1AYC06p8mWPdThEZwpxFF8,2686
164
164
  fprime_gds/flask/static/addons/dictionary/addon-templates.js,sha256=19hYvx_Gf8B5j4po0YKeQrhS2UbUIBAEfqgbDGE2peE,950
165
165
  fprime_gds/flask/static/addons/dictionary/addon.js,sha256=eKggJNvOzj2ssXOenc7ccRUTRgeOJ5d4grWliaxgV40,1350
166
166
  fprime_gds/flask/static/addons/image-display/addon.js,sha256=Uo1JzqsqF4be9B3DAPH-LIm2MhqwUAhvTvobigpZB0Y,1522
167
167
  fprime_gds/flask/static/addons/image-display/dashboard.xml,sha256=t0g0v35YxV85MBhGSR_EkKBLRyglQ2Z7IQwfwvhuJAU,263
168
168
  fprime_gds/flask/static/addons/sequencer/README.md,sha256=-9DULIhPIZIWNSHZmenqrPA-G4OAxmclHukoaHlgIeM,1681
169
169
  fprime_gds/flask/static/addons/sequencer/addon-templates.js,sha256=jdZqBQBAKCwKsv60uWhF1Sd6A6WBeBcZ39yXAUlGpLE,3241
170
- fprime_gds/flask/static/addons/sequencer/addon.js,sha256=lH__umiGj836mEB4BupOBXyLPYRlKZjDq3lICfKJhCU,5116
170
+ fprime_gds/flask/static/addons/sequencer/addon.js,sha256=FX3gkVtA7TuvvjmHNRAZ1wAfJvQ5qk4wtY0o3jYUetc,5188
171
171
  fprime_gds/flask/static/addons/sequencer/autocomplete.js,sha256=O6qT8SjN7xuK0edaCpphENN_dLD8-ceRvE6lPNsZui8,6990
172
172
  fprime_gds/flask/static/addons/sequencer/lint.js,sha256=QPt9bt0SMZxMu6cXFkqILwgLlcTmcO0WbRQE8X2XIbQ,1472
173
173
  fprime_gds/flask/static/addons/sequencer/third/code-mirror.es.js,sha256=u-m1Nh7jnodMrnO1zR7eg0k7Lhwp8nt4nUwwJiKwyuU,813045
@@ -184,7 +184,8 @@ fprime_gds/flask/static/img/success.svg,sha256=wCfYG4cPfSCcsZ76JI4SwAJ-y62rahx9r
184
184
  fprime_gds/flask/static/js/config.js,sha256=3CNrVmUtUGeiomAuoAE22w34r7wA-X0OtXN3JtLZYJ8,1066
185
185
  fprime_gds/flask/static/js/datastore.js,sha256=mx0ZaUq-Nlb2LParYfnhu0WMUH-JGKKKgRQD_nOcZNE,15437
186
186
  fprime_gds/flask/static/js/gds.js,sha256=OeDJrNmNA8hUPi8QIHP-s33MW_IYT3QIccxzL75MsjA,1297
187
- fprime_gds/flask/static/js/loader.js,sha256=cpvd1P6jMaoVovGPR6iOHR5K6LR8Ga0F69qb6dwZ4cI,15521
187
+ fprime_gds/flask/static/js/json.js,sha256=kfwzTqoyLu_Feqcp9WEhDWpZ3lziwHy9LGkOg8PCo6s,12429
188
+ fprime_gds/flask/static/js/loader.js,sha256=IbuJ7Jh70umyZog0iIupOuRGwc1tO5thT88jTW0XZc4,11414
188
189
  fprime_gds/flask/static/js/performance.js,sha256=fGBbK5anf5UB9iAr6rO4u9sCkrQGlOHHQLm1b3nvrvg,5359
189
190
  fprime_gds/flask/static/js/settings.js,sha256=Cfnn1ybZUOmsp48t_b23TDaHBRctvpD_h2ivBVKz6HM,1101
190
191
  fprime_gds/flask/static/js/uploader.js,sha256=HdFUGwJ-SN_OEMXiXXJw0YblC0j_QiOA1y9yVWyS4SI,2730
@@ -227,10 +228,10 @@ fprime_gds/flask/static/third-party/webfonts/fa-solid-900.woff2,sha256=mDS4KtJuK
227
228
  fprime_gds/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
229
  fprime_gds/plugin/definitions.py,sha256=5rHGSOrr62qRNVfX9bZIo4HDAKG62lKteNum9G40y3g,2347
229
230
  fprime_gds/plugin/system.py,sha256=uWd6DVW90Re0FoNMPNCx0cXXTJUdpgAAO0mtakzRNgk,8564
230
- fprime_gds-3.5.2a2.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
231
- fprime_gds-3.5.2a2.dist-info/METADATA,sha256=xq12LG22iaSHz8kLu-zEwshPLqhew6kiUmgpbLeSbbA,24772
232
- fprime_gds-3.5.2a2.dist-info/NOTICE.txt,sha256=vXjA_xRcQhd83Vfk5D_vXg5kOjnnXvLuMi5vFKDEVmg,1612
233
- fprime_gds-3.5.2a2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
234
- fprime_gds-3.5.2a2.dist-info/entry_points.txt,sha256=oqUiO3xhJCR943jdU3zcxbqEvSXNeVgshk7dVaf_nGY,322
235
- fprime_gds-3.5.2a2.dist-info/top_level.txt,sha256=6vzFLIX6ANfavKaXFHDMSLFtS94a6FaAsIWhjgYuSNE,27
236
- fprime_gds-3.5.2a2.dist-info/RECORD,,
231
+ fprime_gds-3.6.1.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
232
+ fprime_gds-3.6.1.dist-info/METADATA,sha256=nxDoqKpYEs-Fv2wpXgiNUpmT1D0fjvGHsYgqjqJT7Hs,24770
233
+ fprime_gds-3.6.1.dist-info/NOTICE.txt,sha256=vXjA_xRcQhd83Vfk5D_vXg5kOjnnXvLuMi5vFKDEVmg,1612
234
+ fprime_gds-3.6.1.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
235
+ fprime_gds-3.6.1.dist-info/entry_points.txt,sha256=oqUiO3xhJCR943jdU3zcxbqEvSXNeVgshk7dVaf_nGY,322
236
+ fprime_gds-3.6.1.dist-info/top_level.txt,sha256=6vzFLIX6ANfavKaXFHDMSLFtS94a6FaAsIWhjgYuSNE,27
237
+ fprime_gds-3.6.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5