openc3-cosmos-script-engine-cstol 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f1bc3e1c0f55e2a66fc5df1c1b156ef06f29a2b98132f1a9ca89d26562ba1fb
4
- data.tar.gz: da77cd0df1e9908ca50654fb5b9a9cbad3b0359353e9009daab56504bab811d1
3
+ metadata.gz: 6ef43bd11a38493eccfc313dccee63034ead05937930eaed62e5c164bd4b68a4
4
+ data.tar.gz: e63152c3a168bf6da0b6d70b3e3aa9f05cb2c974c631cbd4e1ac5f36908618a6
5
5
  SHA512:
6
- metadata.gz: b70bb6b13b4fac767e2bfb3407318c350029c94ba2535f7c4a33a615c2b4deb5491ebcf6f6fd2502f33a8be75d6bbf41a6a495602eb19e9f0d082e6261f48791
7
- data.tar.gz: 2d5ba36b266566e4eda8e6cabe4ed67589411335d3b6c3d2545ab4fb28162ec0b64c7d544ec4b1a8fb2910ae36aa4dd5b80fd0e09263c12808b346dce4d0c9ed
6
+ metadata.gz: eab9091c50b1867c2d4ac4b2794f72abb67bd48352ccbd01cfa14fb442be43314c1766e73ba418cef8445d6f596d7855b54306c6b5a58cddfcca900698450107
7
+ data.tar.gz: d23e20164925cafe6a9efee37d6348b2277a1996a53fe8d05f0b31dd6fa4612635a0e34c1ffdcf893af8a5c297b175ece81a747250459ce747a97e298a3e6ece
@@ -25,12 +25,18 @@ import shlex
25
25
  import datetime
26
26
  import math
27
27
  import os
28
- from openc3.script.exceptions import CheckError, StopScript
28
+ from openc3.script.exceptions import CheckError, StopScriptError
29
29
  from openc3.script_engines.script_engine import ScriptEngine
30
30
  from openc3.script import wait, wait_expression, ask_string, clear_screen, clear_all_screens, set_tlm, display_screen, \
31
31
  connect_interface, disconnect_interface, send_raw, start, cmd, get_target_file, tlm, ask_string, set_line_delay, step_mode, run_mode
32
32
 
33
33
  class CstolVariables:
34
+ # NOTE: special_variables is INTENTIONALLY a class-level (shared) dictionary.
35
+ # Special variables ($$ variables) are global to the engine and must persist
36
+ # across every script run and every CstolVariables instance. Unlike
37
+ # local_variables (which __init__ resets per instance), this state is meant to
38
+ # be shared by all instances. Do NOT move this into __init__ or copy it per
39
+ # instance - that would break the intended global behavior.
34
40
  special_variables = {
35
41
  "$$OWLT": 0.0,
36
42
  "$$ERROR": "NO_ERROR",
@@ -43,8 +49,23 @@ class CstolVariables:
43
49
 
44
50
  def __init__(self):
45
51
  self.local_variables = {}
52
+ self.if_stack = []
46
53
  self.loop_stack = []
47
54
 
55
+ def set_local_variable(self, name, value):
56
+ """
57
+ Sets a local variable. Variable names are case insensitive, so they are
58
+ normalized to upper case before being stored.
59
+ """
60
+ self.local_variables[name.upper()] = value
61
+
62
+ def get_local_variable(self, name):
63
+ """
64
+ Gets a local variable by name (case insensitive).
65
+ Returns None if the variable does not exist.
66
+ """
67
+ return self.local_variables.get(name.upper(), None)
68
+
48
69
  def set_special_variable(self, name, value):
49
70
  """
50
71
  Sets a special variable, which is a variable that starts with '$$'.
@@ -52,8 +73,9 @@ class CstolVariables:
52
73
  """
53
74
  if not name.startswith('$$'):
54
75
  raise ValueError(f"Special variable names must start with '$$': {name}")
76
+ name = name.upper()
55
77
  self.special_variables[name] = value
56
- match name.upper():
78
+ match name:
57
79
  case "$$CLP_STP_INTERVAL":
58
80
  set_line_delay(value)
59
81
  self.special_variables["$$STEP_INTERVAL"] = value
@@ -82,7 +104,8 @@ class CstolVariables:
82
104
  """
83
105
  if not name.startswith('$$'):
84
106
  raise ValueError(f"Special variable names must start with '$$': {name}")
85
- match name.upper():
107
+ name = name.upper()
108
+ match name:
86
109
  case "$$CURRENT_TIME":
87
110
  return datetime.datetime.now(datetime.timezone.utc).timestamp()
88
111
  case "$$SC_TIME":
@@ -240,7 +263,19 @@ class CstolScriptEngine(ScriptEngine):
240
263
  i += 3 # Skip the next 2 tokens since we combined them
241
264
 
242
265
  else:
243
- reconstructed_tokens.append(tokens[i])
266
+ # Recombine multi-part operator tokens
267
+ token = tokens[i]
268
+ if token in self.KNOWN_TOKENS and i + 1 < len(tokens):
269
+ next_token = tokens[i + 1]
270
+ if ((token == '*' and next_token == '*') or
271
+ (token == '<' and next_token == '=') or
272
+ (token == '>' and next_token == '=') or
273
+ (token == '/' and next_token == '=')):
274
+ reconstructed_tokens.append(token + next_token)
275
+ i += 2 # Skip the next token
276
+ continue
277
+
278
+ reconstructed_tokens.append(token)
244
279
  i += 1
245
280
 
246
281
  return reconstructed_tokens
@@ -280,7 +315,7 @@ class CstolScriptEngine(ScriptEngine):
280
315
  # Handle local variables
281
316
  if token.startswith('$'):
282
317
  # Get the actual value of local variable
283
- value = self.variables.local_variables.get(token, None)
318
+ value = self.variables.get_local_variable(token)
284
319
  if value is None:
285
320
  raise ValueError(f"Unknown variable: {token}")
286
321
  if isinstance(value, str):
@@ -312,19 +347,19 @@ class CstolScriptEngine(ScriptEngine):
312
347
  continue
313
348
 
314
349
  # Handle radix notation integers
315
- matches = re.match(r'^[BODXHbodxh]#\d+$', token)
350
+ # Allow hex digits (A-F) for all bases so X#FF and H#DEADBEEF parse;
351
+ # int() with the proper base validates the digits for each radix.
352
+ matches = re.match(r'^([BODXHbodxh])#([0-9A-Fa-f]+)$', token)
316
353
  if matches is not None:
317
- match (token[0].upper()):
318
- case 'B':
319
- final_tokens.append(f'0b{token[2:]}')
320
- case 'O':
321
- final_tokens.append(f'0o{token[2:]}')
322
- case 'D':
323
- final_tokens.append(f'{token[2:]}')
354
+ radix_char = matches.group(1).upper()
355
+ digits = matches.group(2)
356
+ match radix_char:
324
357
  case 'H':
325
- final_tokens.append(str(int(token[2:], 16).to_bytes((len(token[2:]) + 1) // 2, 'big')))
326
- case 'X':
327
- final_tokens.append(f'0x{token[2:]}')
358
+ # Hex byte buffer (raw bytes rather than an integer)
359
+ final_tokens.append(str(int(digits, 16).to_bytes((len(digits) + 1) // 2, 'big')))
360
+ case _:
361
+ base = {'B': 2, 'O': 8, 'D': 10, 'X': 16}[radix_char]
362
+ final_tokens.append(str(int(digits, base)))
328
363
  previous_number = True
329
364
  continue
330
365
 
@@ -359,7 +394,7 @@ class CstolScriptEngine(ScriptEngine):
359
394
  token = final_tokens[i]
360
395
 
361
396
  # Check for "RAW" pattern first: "RAW", "TARGET_NAME", "ITEM_NAME"
362
- if (token == '"RAW"' and i + 2 < len(final_tokens) and
397
+ if (token.upper() == '"RAW"' and i + 2 < len(final_tokens) and
363
398
  final_tokens[i + 1].startswith('"') and final_tokens[i + 1].endswith('"') and
364
399
  final_tokens[i + 2].startswith('"') and final_tokens[i + 2].endswith('"')):
365
400
 
@@ -388,7 +423,7 @@ class CstolScriptEngine(ScriptEngine):
388
423
  return ' '.join(processed_tokens)
389
424
 
390
425
  # Combined regex pattern to match both formats
391
- TIMESTAMP_PATTERN = r'(?:(\d{4})?/(\d{1,3})?-)?(\d{1,2}):(\d{1,2}):(\d{1,2}\.?\d*)'
426
+ TIMESTAMP_PATTERN = r'(?:(\d{4})?/(\d{1,3})?-)?(\d{0,2}):(\d{0,2}):(\d{1,2}\.?\d*)'
392
427
 
393
428
  # Will convert a CSTOL Clock Time or Delta Time into floating point seconds
394
429
  def parse_timestamp(self, timestamp, now = None):
@@ -398,6 +433,10 @@ class CstolScriptEngine(ScriptEngine):
398
433
  matches = re.match(self.TIMESTAMP_PATTERN, timestamp)
399
434
  if matches:
400
435
  year, day_of_year, hour, minute, second = matches.groups()
436
+ if len(hour) == 0:
437
+ hour = 0
438
+ if len(minute) == 0:
439
+ minute = 0
401
440
  result = {
402
441
  'hour': int(hour),
403
442
  'minute': int(minute),
@@ -475,6 +514,29 @@ class CstolScriptEngine(ScriptEngine):
475
514
  expressions.append(expression)
476
515
  return expressions
477
516
 
517
+ def _split_wait_timeout(self, tokens):
518
+ """Split WAIT body tokens into (condition_tokens, timeout_tokens | None).
519
+
520
+ Scans from the end of the token list looking for the last ``OR``
521
+ that is immediately followed by ``FOR`` or ``UNTIL``. That ``OR``
522
+ marks the boundary between the boolean condition and the timeout
523
+ clause.
524
+
525
+ Returns
526
+ -------
527
+ (condition_tokens, timeout_tokens)
528
+ *timeout_tokens* includes the ``FOR``/``UNTIL`` keyword and the
529
+ value that follows. If no ``OR FOR`` / ``OR UNTIL`` sequence is
530
+ found the full token list is returned as the condition and
531
+ *timeout_tokens* is ``None``.
532
+ """
533
+ # Walk backwards so we find the last OR FOR / OR UNTIL (before the timeout).
534
+ # Earlier OR tokens are boolean operators inside the condition.
535
+ for i in range(len(tokens) - 1, 0, -1):
536
+ if tokens[i - 1].upper() == "OR" and tokens[i].upper() in ("FOR", "UNTIL"):
537
+ return tokens[:i - 1], tokens[i:]
538
+ return tokens, None
539
+
478
540
  def split_vs_tokens_on_colon(self, tokens):
479
541
  """
480
542
  Split tokens on colons for VS clause processing, but preserve timestamps.
@@ -537,9 +599,12 @@ class CstolScriptEngine(ScriptEngine):
537
599
  question = question[1:-1]
538
600
  answer = ask_string(question)
539
601
  if answer is not None:
540
- if answer[0] == '"' and answer[-1] == '"':
602
+ if len(answer) >= 2 and answer[0] == '"' and answer[-1] == '"':
541
603
  # Remove quotes and don't uppercase and don't eval
542
604
  answer = answer[1:-1]
605
+ elif answer.strip() == '':
606
+ # Empty answer - store as-is without tokenizing/evaluating
607
+ pass
543
608
  else:
544
609
  # Tokenize answer
545
610
  answer_tokens = self.cstol_tokenizer(answer)
@@ -550,11 +615,13 @@ class CstolScriptEngine(ScriptEngine):
550
615
  # Uppercase
551
616
  if isinstance(answer, str):
552
617
  answer = answer.upper()
553
- self.variables.local_variables[variable] = answer
618
+ self.variables.set_local_variable(variable, answer)
554
619
 
555
620
  def handle_check(self, tokens, line_no):
556
621
  expressions = self.extract_expressions(tokens[1:], ",")
557
622
  for expr in expressions:
623
+ if len(expr) == 0:
624
+ raise ValueError(f"Empty expression in CHECK command at line {line_no}")
558
625
  format = None
559
626
  if expr[0][0] == '%':
560
627
  # Format string
@@ -742,7 +809,7 @@ class CstolScriptEngine(ScriptEngine):
742
809
  raise ValueError(f"Expected '=' after variable name in DECLARE command at line {line_no}")
743
810
  #default_value = self.token_to_value(tokens[4])
744
811
  default_value = self.evaluate_tokens([tokens[4]])[0]
745
- self.variables.local_variables[variable_name] = default_value
812
+ self.variables.set_local_variable(variable_name, default_value)
746
813
 
747
814
  def handle_display(self, tokens, line_no):
748
815
  screen_name = tokens[1]
@@ -752,9 +819,29 @@ class CstolScriptEngine(ScriptEngine):
752
819
  display_screen(*screen_name.split())
753
820
 
754
821
  def handle_else(self, tokens, lines, line_no):
755
- if len(tokens) == 1 and tokens[0] == 'ELSE':
822
+ goto_endif = False
823
+ if len(tokens) > 1:
824
+ # ELSE IF is tricky - We can hit these after a successful IF, or an unsuccessful IF
825
+ # The if_stack keeps track if an earlier if was successful and its value will determine if we
826
+ # execute the ELSEIF or just goto the next ENDIF
827
+ current_if = False
828
+ if len(self.variables.if_stack) > 0:
829
+ current_if = self.variables.if_stack[-1]
830
+ if tokens[0].upper() == 'ELSEIF':
831
+ if current_if:
832
+ goto_endif = True
833
+ else:
834
+ return self.handle_if(tokens, lines, line_no)
835
+ elif (tokens[0].upper() == 'ELSE' and tokens[1].upper() == 'IF'):
836
+ if current_if:
837
+ goto_endif = True
838
+ else:
839
+ return self.handle_if(tokens[1:], lines, line_no)
840
+
841
+ if goto_endif or (len(tokens) == 1 and tokens[0].upper() == 'ELSE'):
756
842
  # The only way we ever hit an ELSE is if we were in a successful block beforehand
757
843
  # Therefore goto the ENDIF
844
+ self.variables.if_stack[-1] = True
758
845
  depth = 1
759
846
  for i in range(line_no, len(lines)):
760
847
  next_line = lines[i].strip().upper()
@@ -766,18 +853,16 @@ class CstolScriptEngine(ScriptEngine):
766
853
  return i + 1
767
854
  raise ValueError(f"No matching ENDIF for ELSE command at line {line_no}")
768
855
 
769
- elif len(tokens) > 1:
770
- if tokens[0] == 'ELSEIF':
771
- return self.handle_if(tokens, lines, line_no)
772
- elif (tokens[0] == 'ELSE' and tokens[1] == 'IF'):
773
- return self.handle_if(tokens[1:], lines, line_no)
856
+ raise ValueError(f"handle_else called with unexpected tokens at line {line_no}")
774
857
 
775
858
  def handle_end(self, tokens, line_no):
776
- if len(tokens) == 1 and tokens[0] == 'END':
859
+ if len(tokens) == 1 and tokens[0].upper() == 'END':
777
860
  raise ValueError(f"Unexpected END command at line {line_no}, expected ENDIF, ENDLOOP, ENDMACRO, or ENDPROC")
778
- elif (len(tokens) == 1 and tokens[0] == 'ENDIF') or (tokens[0] == 'END' and tokens[1] == 'IF'):
861
+ elif (len(tokens) == 1 and tokens[0].upper() == 'ENDIF') or (tokens[0].upper() == 'END' and tokens[1].upper() == 'IF'):
862
+ # END IF pops the IF stack
863
+ self.variables.if_stack.pop()
779
864
  pass
780
- elif (len(tokens) == 1 and tokens[0] == 'ENDLOOP') or (tokens[0] == 'END' and tokens[1] == 'LOOP'):
865
+ elif (len(tokens) == 1 and tokens[0].upper() == 'ENDLOOP') or (tokens[0].upper() == 'END' and tokens[1].upper() == 'LOOP'):
781
866
  if len(self.variables.loop_stack) > 0:
782
867
  loop_info = self.variables.loop_stack[-1]
783
868
  loop_info[2] += 1
@@ -798,10 +883,14 @@ class CstolScriptEngine(ScriptEngine):
798
883
  # Find the matching ENDLOOP
799
884
  depth = 1
800
885
  for i in range(line_no, len(lines)):
801
- next_line = lines[i].strip()
802
- if next_line.startswith('LOOP'):
886
+ # Match on whole words so labels like "LOOPBACK:" are not mistaken
887
+ # for a nested LOOP keyword
888
+ words = lines[i].strip().upper().split()
889
+ if not words:
890
+ continue
891
+ if words[0] == 'LOOP':
803
892
  depth += 1
804
- elif next_line.startswith('END LOOP') or next_line.startswith('ENDLOOP'):
893
+ elif words[0] == 'ENDLOOP' or (words[0] == 'END' and len(words) > 1 and words[1] == 'LOOP'):
805
894
  depth -= 1
806
895
  if depth == 0:
807
896
  if len(self.variables.loop_stack) > 0:
@@ -810,17 +899,17 @@ class CstolScriptEngine(ScriptEngine):
810
899
  raise ValueError(f"No matching ENDLOOP found for ESCAPE command at line {line_no}")
811
900
 
812
901
  def handle_go(self, tokens, lines, line_no):
813
- if tokens[0] == 'GOTO' or (len(tokens) > 1 and tokens[0] == 'GO' and tokens[1] == 'TO'):
814
- if (tokens[0] == 'GOTO' and len(tokens) < 2) or (tokens[0] == 'GO' and tokens[1] != 'TO' and len(tokens) < 3):
902
+ if tokens[0].upper() == 'GOTO' or (len(tokens) > 1 and tokens[0].upper() == 'GO' and tokens[1].upper() == 'TO'):
903
+ if (tokens[0].upper() == 'GOTO' and len(tokens) < 2) or (tokens[0].upper() == 'GO' and len(tokens) < 3):
815
904
  raise ValueError(f"Invalid GOTO command format at line {line_no}")
816
905
  label = None
817
- if tokens[0] == 'GOTO':
818
- label = tokens[1] + ':'
906
+ if tokens[0].upper() == 'GOTO':
907
+ label = (tokens[1] + ':').upper()
819
908
  else:
820
- label = tokens[2] + ':'
909
+ label = (tokens[2] + ':').upper()
821
910
  # Find the label in the lines
822
911
  for i in range(1, len(lines)):
823
- next_line = lines[i - 1].strip()
912
+ next_line = lines[i - 1].strip().upper()
824
913
  if next_line.startswith(label):
825
914
  return i
826
915
  raise ValueError(f"Label '{label}' not found in GOTO command at line {line_no}")
@@ -835,6 +924,8 @@ class CstolScriptEngine(ScriptEngine):
835
924
  raise ValueError(f"Error evaluating expression '{expression_tokens}' in IF command at line {line_no}: {e}")
836
925
 
837
926
  if result:
927
+ # Mark if as handled
928
+ self.variables.if_stack[-1] = True
838
929
  return line_no + 1 # Continue to the next line if the condition is true
839
930
  # If the condition is false, skip to the next ENDIF or ELSE
840
931
  else:
@@ -897,7 +988,7 @@ class CstolScriptEngine(ScriptEngine):
897
988
  self.variables.set_special_variable(variable_name, result)
898
989
  else:
899
990
  # Local variable
900
- self.variables.local_variables[variable_name] = result
991
+ self.variables.set_local_variable(variable_name, result)
901
992
 
902
993
  def handle_load(self, tokens, line_no):
903
994
  # LOAD external-element-name AT location FROM file-name
@@ -923,14 +1014,11 @@ class CstolScriptEngine(ScriptEngine):
923
1014
  if len(tokens) == 1:
924
1015
  # Infinite loop
925
1016
  self.variables.loop_stack.append([line_no + 1, None, 1])
926
- return line_no + 1 # Continue to the next line
927
- elif len(tokens) == 2:
928
- # Counted loop
929
- count = int(tokens[1])
930
- self.variables.loop_stack.append([line_no + 1, count, 1])
931
- return line_no + 1 # Continue to the next line
932
1017
  else:
933
- raise ValueError(f"Invalid LOOP command format at line {line_no}")
1018
+ # Counted loop - the count may be a literal, variable, or expression
1019
+ count = int(self.evaluate_expression(tokens[1:]))
1020
+ self.variables.loop_stack.append([line_no + 1, count, 1])
1021
+ return line_no + 1 # Continue to the next line
934
1022
 
935
1023
  def handle_proc(self, tokens, line_no):
936
1024
  # tokens[1] is proc name
@@ -941,7 +1029,7 @@ class CstolScriptEngine(ScriptEngine):
941
1029
  if token == ',':
942
1030
  continue
943
1031
  elif token.startswith('$'):
944
- self.variables.local_variables[token] = os.getenv(f"CSTOL_ARG_{index}")
1032
+ self.variables.set_local_variable(token, os.getenv(f"CSTOL_ARG_{index}"))
945
1033
  index += 1
946
1034
  else:
947
1035
  raise ValueError(f"Invalid variable '{token}' in PROC command at line {line_no}")
@@ -949,7 +1037,7 @@ class CstolScriptEngine(ScriptEngine):
949
1037
  def handle_return(self, tokens, lines, line_no):
950
1038
  if len(tokens) > 1:
951
1039
  if tokens[1].upper() == 'ALL':
952
- raise StopScript
1040
+ raise StopScriptError
953
1041
  return (len(lines) + 1)
954
1042
 
955
1043
  def handle_run(self, tokens, line_no):
@@ -1023,11 +1111,11 @@ class CstolScriptEngine(ScriptEngine):
1023
1111
  raise ValueError(f"Invalid timestamp format at line {line_no}")
1024
1112
  else:
1025
1113
  # Conditional expression wait
1026
- expressions = self.extract_expressions(tokens[1:], seperator="OR")
1027
- python_expression = self.build_python_expression(expressions[0])
1028
- if len(expressions) > 1:
1114
+ condition_tokens, timeout_tokens = self._split_wait_timeout(tokens[1:])
1115
+ python_expression = self.build_python_expression(condition_tokens)
1116
+ if timeout_tokens is not None:
1029
1117
  # Timeout given with "OR FOR" or "OR UNTIL"
1030
- seconds = self.evaluate_expression(expressions[1][1:]) # Drop assumed FOR or UNTIL token
1118
+ seconds = self.evaluate_expression(timeout_tokens[1:]) # Drop FOR or UNTIL token
1031
1119
  if isinstance(seconds, (int, float, complex)) and not isinstance(seconds, bool):
1032
1120
  if seconds > self.ONE_YEAR_SECONDS:
1033
1121
  now = datetime.datetime.now().timestamp()
@@ -1052,6 +1140,8 @@ class CstolScriptEngine(ScriptEngine):
1052
1140
  expressions = self.extract_expressions(tokens[1:], ",")
1053
1141
  results = []
1054
1142
  for expr in expressions:
1143
+ if len(expr) == 0:
1144
+ raise ValueError(f"Empty expression in WRITE command at line {line_no}")
1055
1145
  result = ''
1056
1146
  if expr[0][0] == '%':
1057
1147
  # Format string
@@ -1166,6 +1256,8 @@ class CstolScriptEngine(ScriptEngine):
1166
1256
  case "GO" | "GOTO":
1167
1257
  return self.handle_go(tokens, lines, line_no)
1168
1258
  case "IF":
1259
+ # Regular IF starts an if stack
1260
+ self.variables.if_stack.append(False)
1169
1261
  return self.handle_if(tokens, lines, line_no)
1170
1262
  case "LET":
1171
1263
  self.handle_let(tokens, line_no)
@@ -1192,4 +1284,4 @@ class CstolScriptEngine(ScriptEngine):
1192
1284
  case _:
1193
1285
  raise ValueError(f"Unknown keyword '{keyword}' at line {line_no}")
1194
1286
 
1195
- return line_no + 1
1287
+ return line_no + 1
Binary file
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openc3-cosmos-script-engine-cstol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
- - Ryan Melton
7
+ - OpenC3, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-17 00:00:00.000000000 Z
11
+ date: 2026-08-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: " This plugin provides a script engine to support CSTOL in Script Runner\n"
14
14
  email:
15
- - ryan@openc3.com
15
+ - plugins@openc3.com
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
@@ -22,12 +22,19 @@ files:
22
22
  - Rakefile
23
23
  - lib/cstol_script_engine.py
24
24
  - plugin.txt
25
+ - public/store_img.png
25
26
  - targets/CSTOL_TEST/procedures/collect.prc
26
27
  - targets/CSTOL_TEST/procedures/test.prc
27
28
  homepage: https://github.com/OpenC3/openc3-cosmos-script-engine-cstol
28
29
  licenses:
29
30
  - Nonstandard
30
- metadata: {}
31
+ metadata:
32
+ source_code_uri: https://github.com/OpenC3/openc3-cosmos-script-engine-cstol
33
+ openc3_store_title: CSTOL Script Engine
34
+ openc3_store_keywords: script, cstol
35
+ openc3_store_image: public/store_img.png
36
+ openc3_store_access_type: public
37
+ openc3_cosmos_minimum_version: 7.0.0
31
38
  post_install_message:
32
39
  rdoc_options: []
33
40
  require_paths:
@@ -46,5 +53,5 @@ requirements: []
46
53
  rubygems_version: 3.5.22
47
54
  signing_key:
48
55
  specification_version: 4
49
- summary: OpenC3 Script Engine CSTOL
56
+ summary: OpenC3 CSTOL Script Engine
50
57
  test_files: []