ivoryos 0.1.9__py3-none-any.whl → 0.1.10__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.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (37) hide show
  1. ivoryos/__init__.py +118 -99
  2. ivoryos/config.py +47 -47
  3. ivoryos/routes/auth/auth.py +100 -65
  4. ivoryos/routes/auth/templates/auth/login.html +25 -25
  5. ivoryos/routes/auth/templates/auth/signup.html +32 -32
  6. ivoryos/routes/control/control.py +400 -272
  7. ivoryos/routes/control/templates/control/controllers.html +75 -75
  8. ivoryos/routes/control/templates/control/controllers_home.html +50 -50
  9. ivoryos/routes/control/templates/control/controllers_new.html +89 -89
  10. ivoryos/routes/database/database.py +188 -114
  11. ivoryos/routes/database/templates/database/experiment_database.html +72 -72
  12. ivoryos/routes/design/design.py +541 -416
  13. ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
  14. ivoryos/routes/design/templates/design/experiment_run.html +325 -325
  15. ivoryos/routes/main/main.py +42 -25
  16. ivoryos/routes/main/templates/main/help.html +141 -141
  17. ivoryos/routes/main/templates/main/home.html +68 -68
  18. ivoryos/static/.DS_Store +0 -0
  19. ivoryos/static/js/overlay.js +12 -12
  20. ivoryos/static/js/socket_handler.js +34 -34
  21. ivoryos/static/js/sortable_card.js +24 -24
  22. ivoryos/static/js/sortable_design.js +36 -36
  23. ivoryos/static/style.css +201 -201
  24. ivoryos/templates/base.html +143 -143
  25. ivoryos/utils/db_models.py +518 -518
  26. ivoryos/utils/form.py +316 -316
  27. ivoryos/utils/global_config.py +67 -67
  28. ivoryos/utils/llm_agent.py +183 -183
  29. ivoryos/utils/script_runner.py +165 -164
  30. ivoryos/utils/utils.py +425 -422
  31. ivoryos/version.py +1 -0
  32. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
  33. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -169
  34. ivoryos-0.1.10.dist-info/RECORD +47 -0
  35. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
  36. ivoryos-0.1.9.dist-info/RECORD +0 -45
  37. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
@@ -1,518 +1,518 @@
1
- import json
2
- import keyword
3
- import re
4
- import uuid
5
- from datetime import datetime
6
-
7
- from flask_login import UserMixin
8
- from flask_sqlalchemy import SQLAlchemy
9
- from sqlalchemy_utils import JSONType
10
-
11
- db = SQLAlchemy()
12
-
13
-
14
- class User(db.Model, UserMixin):
15
- __tablename__ = 'user'
16
- # id = db.Column(db.Integer)
17
- username = db.Column(db.String(50), primary_key=True, unique=True, nullable=False)
18
- # email = db.Column(db.String)
19
- hashPassword = db.Column(db.String(255))
20
-
21
- # password = db.Column()
22
- def __init__(self, username, password):
23
- # self.id = id
24
- self.username = username
25
- # self.email = email
26
- self.hashPassword = password
27
-
28
- def get_id(self):
29
- return self.username
30
-
31
-
32
- class Script(db.Model):
33
- __tablename__ = 'script'
34
- # id = db.Column(db.Integer, primary_key=True)
35
- name = db.Column(db.String(50), primary_key=True, unique=True)
36
- deck = db.Column(db.String(50), nullable=True)
37
- status = db.Column(db.String(50), nullable=True)
38
- script_dict = db.Column(JSONType, nullable=True)
39
- time_created = db.Column(db.String(50), nullable=True)
40
- last_modified = db.Column(db.String(50), nullable=True)
41
- id_order = db.Column(JSONType, nullable=True)
42
- editing_type = db.Column(db.String(50), nullable=True)
43
- author = db.Column(db.String(50), nullable=False)
44
-
45
- def __init__(self, name=None, deck=None, status=None, script_dict: dict = None, id_order: dict = None,
46
- time_created=None, last_modified=None, editing_type=None, author: str = None):
47
- if script_dict is None:
48
- script_dict = {"prep": [], "script": [], "cleanup": []}
49
- elif type(script_dict) is not dict:
50
- script_dict = json.loads(script_dict)
51
- if id_order is None:
52
- id_order = {"prep": [], "script": [], "cleanup": []}
53
- elif type(id_order) is not dict:
54
- id_order = json.loads(id_order)
55
- if status is None:
56
- status = 'editing'
57
- if time_created is None:
58
- time_created = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
59
- if last_modified is None:
60
- last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
61
- if editing_type is None:
62
- editing_type = "script"
63
-
64
- self.name = name
65
- self.deck = deck
66
- self.status = status
67
- self.script_dict = script_dict
68
- self.time_created = time_created
69
- self.last_modified = last_modified
70
- self.id_order = id_order
71
- self.editing_type = editing_type
72
- self.author = author
73
-
74
- def as_dict(self):
75
- dict = self.__dict__
76
- dict.pop('_sa_instance_state', None)
77
- return dict
78
-
79
- def get(self):
80
- workflows = db.session.query(Script).all()
81
- # result = script_schema.dump(workflows)
82
- return workflows
83
-
84
- def find_by_uuid(self, uuid):
85
- for stype in self.script_dict:
86
- for action in self.script_dict[stype]:
87
-
88
- if action['uuid'] == int(uuid):
89
- return action
90
-
91
- def _convert_type(self, args, arg_types):
92
- if type(arg_types) is not list:
93
- arg_types = [arg_types]
94
- for arg_type in arg_types:
95
- try:
96
- args = eval(f"{arg_type}('{args}')")
97
- return
98
- except Exception:
99
- pass
100
- raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
101
-
102
- def update_by_uuid(self, uuid, args, output):
103
- bool_dict = {"True": True, "False": False}
104
- action = self.find_by_uuid(uuid)
105
- if type(action['args']) is dict:
106
- for arg in action['args']:
107
- if not args[arg].startswith("#"):
108
-
109
- if args[arg] in bool_dict.keys():
110
- args[arg] = bool_dict[args[arg]]
111
- elif args[arg] == "None" or args[arg] == "":
112
- args[arg] = None
113
- else:
114
- if arg in action['arg_types']:
115
- arg_types = action['arg_types'][arg]
116
- self._convert_type(args[arg], arg_types)
117
- else:
118
- try:
119
- args[arg] = eval(args[arg])
120
- except Exception:
121
- pass
122
- else:
123
- args = list(args.values())[0]
124
- if not args.startswith("#"):
125
- if args in bool_dict.keys():
126
- args = bool_dict[args]
127
-
128
- else:
129
- if 'arg_types' in action:
130
- arg_types = action['arg_types']
131
- self._convert_type(args, arg_types)
132
-
133
- # print(args)
134
- action['args'] = args
135
- # print(action)
136
- action['return'] = output
137
-
138
- @property
139
- def stypes(self):
140
- return list(self.script_dict.keys())
141
-
142
- @property
143
- def currently_editing_script(self):
144
- return self.script_dict[self.editing_type]
145
-
146
- @currently_editing_script.setter
147
- def currently_editing_script(self, script):
148
- self.script_dict[self.editing_type] = script
149
-
150
- @property
151
- def currently_editing_order(self):
152
- return self.id_order[self.editing_type]
153
-
154
- @currently_editing_order.setter
155
- def currently_editing_order(self, script):
156
- self.id_order[self.editing_type] = script
157
-
158
- # @property
159
- # def editing_type(self):
160
- # return self.editing_type
161
-
162
- # @editing_type.setter
163
- # def editing_type(self, change_type):
164
- # self.editing_type = change_type
165
-
166
- def update_time_stamp(self):
167
- self.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
168
-
169
- def get_script(self, stype: str):
170
- return self.script_dict[stype]
171
-
172
- def isEmpty(self) -> bool:
173
- if not (self.script_dict['script'] or self.script_dict['prep'] or self.script_dict['cleanup']):
174
- return True
175
- return False
176
-
177
- def _sort(self, script_type):
178
- if len(self.id_order[script_type]) > 0:
179
- for action in self.script_dict[script_type]:
180
- for i in range(len(self.id_order[script_type])):
181
- if action['id'] == int(self.id_order[script_type][i]):
182
- # print(i+1)
183
- action['id'] = i + 1
184
- break
185
- self.id_order[script_type].sort()
186
- if not int(self.id_order[script_type][-1]) == len(self.script_dict[script_type]):
187
- new_order = list(range(1, len(self.script_dict[script_type]) + 1))
188
- self.id_order[script_type] = [str(i) for i in new_order]
189
- self.script_dict[script_type].sort(key=lambda x: x['id'])
190
-
191
- def sort_actions(self, script_type=None):
192
- if script_type:
193
- self._sort(script_type)
194
- else:
195
- for i in self.stypes:
196
- self._sort(i)
197
-
198
- def add_action(self, action: dict):
199
- current_len = len(self.currently_editing_script)
200
- action_to_add = action.copy()
201
- action_to_add['id'] = current_len + 1
202
- action_to_add['uuid'] = uuid.uuid4().fields[-1]
203
- self.currently_editing_script.append(action_to_add)
204
- self.currently_editing_order.append(str(current_len + 1))
205
- self.update_time_stamp()
206
-
207
- def add_variable(self, statement, variable):
208
- current_len = len(self.currently_editing_script)
209
- uid = uuid.uuid4().fields[-1]
210
- action_list = [{"id": current_len + 1, "instrument": 'variable', "action": variable,
211
- "args": 'None' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''}]
212
- self.currently_editing_script.extend(action_list)
213
- self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
214
- self.update_time_stamp()
215
-
216
- def add_logic_action(self, logic_type: str, statement):
217
- current_len = len(self.currently_editing_script)
218
- uid = uuid.uuid4().fields[-1]
219
- logic_dict = {
220
- "if":
221
- [
222
- {"id": current_len + 1, "instrument": 'if', "action": 'if',
223
- "args": 'True' if statement == '' else statement,
224
- "return": '', "uuid": uid, "arg_types": ''},
225
- {"id": current_len + 2, "instrument": 'if', "action": 'else', "args": '', "return": '',
226
- "uuid": uid},
227
- {"id": current_len + 3, "instrument": 'if', "action": 'endif', "args": '', "return": '',
228
- "uuid": uid},
229
- ],
230
- "while":
231
- [
232
- {"id": current_len + 1, "instrument": 'while', "action": 'while',
233
- "args": 'False' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''},
234
- {"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": '', "return": '',
235
- "uuid": uid},
236
- ],
237
-
238
- "wait":
239
- [
240
- {"id": current_len + 1, "instrument": 'wait', "action": "wait",
241
- "args": '0' if statement == '' else statement,
242
- "return": '', "uuid": uid, "arg_types": "float"},
243
- ],
244
- }
245
- action_list = logic_dict[logic_type]
246
- self.currently_editing_script.extend(action_list)
247
- self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
248
- self.update_time_stamp()
249
-
250
- def delete_action(self, id: int):
251
-
252
- uid = next((action['uuid'] for action in self.currently_editing_script if action['id'] == int(id)), None)
253
- id_to_be_removed = [action['id'] for action in self.currently_editing_script if action['uuid'] == uid]
254
- order = self.currently_editing_order
255
- script = self.currently_editing_script
256
- self.currently_editing_order = [i for i in order if int(i) not in id_to_be_removed]
257
- self.currently_editing_script = [action for action in script if action['id'] not in id_to_be_removed]
258
- self.sort_actions()
259
- self.update_time_stamp()
260
-
261
- def duplicate_action(self, id: int):
262
- action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)), None)
263
- insert_id = action_to_duplicate.get("id")
264
- self.add_action(action_to_duplicate)
265
- # print(self.currently_editing_script)
266
- if action_to_duplicate is not None:
267
- # Update IDs for all subsequent actions
268
- for action in self.currently_editing_script:
269
- if action['id'] > insert_id:
270
- action['id'] += 1
271
- self.currently_editing_script[-1]['id'] = insert_id + 1
272
- # Sort actions if necessary and update the time stamp
273
- self.sort_actions()
274
- self.update_time_stamp()
275
- else:
276
- raise ValueError("Action not found: Unable to duplicate the action with ID", id)
277
-
278
- def config(self, stype):
279
- """
280
- take the global script_dict
281
- :return: list of variable that require input
282
- """
283
- configure = []
284
- config_type_dict = {}
285
- for action in self.script_dict[stype]:
286
- args = action['args']
287
- if args is not None:
288
- if type(args) is not dict:
289
- if type(args) is str and args.startswith("#") and not args[1:] in configure:
290
- configure.append(args[1:])
291
- config_type_dict[args[1:]] = action['arg_types']
292
-
293
- else:
294
- for arg in args:
295
- if type(args[arg]) is str \
296
- and args[arg].startswith("#") \
297
- and not args[arg][1:] in configure:
298
- configure.append(args[arg][1:])
299
- if arg in action['arg_types']:
300
- if action['arg_types'][arg] == '':
301
- config_type_dict[args[arg][1:]] = "any"
302
- else:
303
- config_type_dict[args[arg][1:]] = action['arg_types'][arg]
304
- else:
305
- config_type_dict[args[arg][1:]] = "any"
306
- # todo
307
- return configure, config_type_dict
308
-
309
- def config_return(self):
310
- """
311
- take the global script_dict
312
- :return: list of variable that require input
313
- """
314
-
315
- return_list = [action['return'] for action in self.script_dict['script'] if not action['return'] == '']
316
- output_str = "return {"
317
- for i in return_list:
318
- output_str += "'" + i + "':" + i + ","
319
- output_str += "}"
320
- return output_str, return_list
321
-
322
- def finalize(self):
323
- self.status = "finalized"
324
- self.update_time_stamp()
325
-
326
- def save_as(self, name):
327
- self.name = name
328
- self.status = "editing"
329
- self.update_time_stamp()
330
-
331
- def indent(self, unit=0):
332
- string = "\n"
333
- for _ in range(unit):
334
- string += "\t"
335
- return string
336
-
337
- def compile(self, script_path=None):
338
- """
339
- Compile the current script to a Python file.
340
- :return: String to write to a Python file.
341
- """
342
- self.sort_actions()
343
- run_name = self.name if self.name else "untitled"
344
- run_name = self.validate_function_name(run_name)
345
- exec_string = ''
346
-
347
- for i in self.stypes:
348
- exec_string += self._generate_function_header(run_name, i)
349
- exec_string += self._generate_function_body(i)
350
-
351
- if script_path:
352
- self._write_to_file(script_path, run_name, exec_string)
353
-
354
- return exec_string
355
-
356
- @staticmethod
357
- def validate_function_name(name):
358
- """Replace invalid characters with underscores"""
359
- name = re.sub(r'\W|^(?=\d)', '_', name)
360
- # Check if it's a Python keyword and adjust if necessary
361
- if keyword.iskeyword(name):
362
- name += '_'
363
- return name
364
-
365
- def _generate_function_header(self, run_name, stype):
366
- """
367
- Generate the function header.
368
- """
369
- configure, _ = self.config(stype)
370
- function_header = f"\n\ndef {run_name}_{stype}("
371
-
372
- if stype == "script":
373
- function_header += ",".join(configure)
374
-
375
- function_header += "):"
376
- function_header += self.indent(1) + f"global {run_name}_{stype}"
377
- return function_header
378
-
379
- def _generate_function_body(self, stype):
380
- """
381
- Generate the function body for each type in stypes.
382
- """
383
- body = ''
384
- indent_unit = 1
385
-
386
- for index, action in enumerate(self.script_dict[stype]):
387
- text, indent_unit = self._process_action(indent_unit, action, index, stype)
388
- body += text
389
- return_str, return_list = self.config_return()
390
- if return_list and stype == "script":
391
- body += self.indent(indent_unit) + return_str
392
-
393
- return body
394
-
395
- def _process_action(self, indent_unit, action, index, stype):
396
- """
397
- Process each action within the script dictionary.
398
- """
399
- instrument = action['instrument']
400
- args = self._process_args(action['args'])
401
- save_data = action['return']
402
- action_name = action['action']
403
- next_action = self._get_next_action(stype, index)
404
- if instrument == 'if':
405
- return self._process_if(indent_unit, action_name, args, next_action)
406
- elif instrument == 'while':
407
- return self._process_while(indent_unit, action_name, args, next_action)
408
- elif instrument == 'variable':
409
- return self.indent(indent_unit) + f"{action_name} = {args}", indent_unit
410
- elif instrument == 'wait':
411
- return f"{self.indent(indent_unit)}time.sleep({args})", indent_unit
412
- else:
413
- return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
414
-
415
- def _process_args(self, args):
416
- """
417
- Process arguments, handling any specific formatting needs.
418
- """
419
- if isinstance(args, str) and args.startswith("#"):
420
- return args[1:]
421
- return args
422
-
423
- def _process_if(self, indent_unit, action, args, next_action):
424
- """
425
- Process 'if' and 'else' actions.
426
- """
427
- exec_string = ""
428
- if action == 'if':
429
- exec_string += self.indent(indent_unit) + f"if {args}:"
430
- indent_unit += 1
431
- if next_action and next_action['instrument'] == 'if' and next_action['action'] == 'else':
432
- exec_string += self.indent(indent_unit) + "pass"
433
- # else:
434
-
435
- elif action == 'else':
436
- indent_unit -= 1
437
- exec_string += self.indent(indent_unit) + "else:"
438
- indent_unit += 1
439
- if next_action and next_action['instrument'] == 'if' and next_action['action'] == 'endif':
440
- exec_string += self.indent(indent_unit) + "pass"
441
- else:
442
- indent_unit -= 1
443
- return exec_string, indent_unit
444
-
445
- def _process_while(self, indent_unit, action, args, next_action):
446
- """
447
- Process 'while' and 'endwhile' actions.
448
- """
449
- exec_string = ""
450
- if action == 'while':
451
- exec_string += self.indent(indent_unit) + f"while {args}:"
452
- indent_unit += 1
453
- if next_action and next_action['instrument'] == 'while':
454
- exec_string += self.indent(indent_unit) + "pass"
455
- elif action == 'endwhile':
456
- indent_unit -= 1
457
- return exec_string, indent_unit
458
-
459
- def _process_instrument_action(self, indent_unit, instrument, action, args, save_data):
460
- """
461
- Process actions related to instruments.
462
- """
463
- if isinstance(args, dict):
464
- args_str = self._process_dict_args(args)
465
- single_line = f"{instrument}.{action}(**{args_str})"
466
- elif isinstance(args, str):
467
- single_line = f"{instrument}.{action} = {args}"
468
- else:
469
- single_line = f"{instrument}.{action}()"
470
-
471
- if save_data:
472
- save_data += " = "
473
-
474
- return self.indent(indent_unit) + save_data + single_line, indent_unit
475
-
476
- def _process_dict_args(self, args):
477
- """
478
- Process dictionary arguments, handling special cases like variables.
479
- """
480
- args_str = args.__str__()
481
- for arg in args:
482
- if isinstance(args[arg], str) and args[arg].startswith("#"):
483
- args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
484
- elif self._is_variable(arg):
485
- args_str = args_str.replace(f"'{args[arg]}'", args[arg])
486
- return args_str
487
-
488
- def _get_next_action(self, stype, index):
489
- """
490
- Get the next action in the sequence if it exists.
491
- """
492
- if index < (len(self.script_dict[stype]) - 1):
493
- return self.script_dict[stype][index + 1]
494
- return None
495
-
496
- def _is_variable(self, arg):
497
- """
498
- Check if the argument is of type 'variable'.
499
- """
500
- return arg in self.script_dict and self.script_dict[arg].get("arg_types") == "variable"
501
-
502
- def _write_to_file(self, script_path, run_name, exec_string):
503
- """
504
- Write the compiled script to a file.
505
- """
506
- with open(script_path + run_name + ".py", "w") as s:
507
- if self.deck:
508
- s.write(f"import {self.deck} as deck")
509
- else:
510
- s.write("deck = None")
511
- s.write("\nimport time")
512
- s.write(exec_string)
513
-
514
-
515
- if __name__ == "__main__":
516
- a = Script()
517
-
518
- print("")
1
+ import json
2
+ import keyword
3
+ import re
4
+ import uuid
5
+ from datetime import datetime
6
+
7
+ from flask_login import UserMixin
8
+ from flask_sqlalchemy import SQLAlchemy
9
+ from sqlalchemy_utils import JSONType
10
+
11
+ db = SQLAlchemy()
12
+
13
+
14
+ class User(db.Model, UserMixin):
15
+ __tablename__ = 'user'
16
+ # id = db.Column(db.Integer)
17
+ username = db.Column(db.String(50), primary_key=True, unique=True, nullable=False)
18
+ # email = db.Column(db.String)
19
+ hashPassword = db.Column(db.String(255))
20
+
21
+ # password = db.Column()
22
+ def __init__(self, username, password):
23
+ # self.id = id
24
+ self.username = username
25
+ # self.email = email
26
+ self.hashPassword = password
27
+
28
+ def get_id(self):
29
+ return self.username
30
+
31
+
32
+ class Script(db.Model):
33
+ __tablename__ = 'script'
34
+ # id = db.Column(db.Integer, primary_key=True)
35
+ name = db.Column(db.String(50), primary_key=True, unique=True)
36
+ deck = db.Column(db.String(50), nullable=True)
37
+ status = db.Column(db.String(50), nullable=True)
38
+ script_dict = db.Column(JSONType, nullable=True)
39
+ time_created = db.Column(db.String(50), nullable=True)
40
+ last_modified = db.Column(db.String(50), nullable=True)
41
+ id_order = db.Column(JSONType, nullable=True)
42
+ editing_type = db.Column(db.String(50), nullable=True)
43
+ author = db.Column(db.String(50), nullable=False)
44
+
45
+ def __init__(self, name=None, deck=None, status=None, script_dict: dict = None, id_order: dict = None,
46
+ time_created=None, last_modified=None, editing_type=None, author: str = None):
47
+ if script_dict is None:
48
+ script_dict = {"prep": [], "script": [], "cleanup": []}
49
+ elif type(script_dict) is not dict:
50
+ script_dict = json.loads(script_dict)
51
+ if id_order is None:
52
+ id_order = {"prep": [], "script": [], "cleanup": []}
53
+ elif type(id_order) is not dict:
54
+ id_order = json.loads(id_order)
55
+ if status is None:
56
+ status = 'editing'
57
+ if time_created is None:
58
+ time_created = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
59
+ if last_modified is None:
60
+ last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
61
+ if editing_type is None:
62
+ editing_type = "script"
63
+
64
+ self.name = name
65
+ self.deck = deck
66
+ self.status = status
67
+ self.script_dict = script_dict
68
+ self.time_created = time_created
69
+ self.last_modified = last_modified
70
+ self.id_order = id_order
71
+ self.editing_type = editing_type
72
+ self.author = author
73
+
74
+ def as_dict(self):
75
+ dict = self.__dict__
76
+ dict.pop('_sa_instance_state', None)
77
+ return dict
78
+
79
+ def get(self):
80
+ workflows = db.session.query(Script).all()
81
+ # result = script_schema.dump(workflows)
82
+ return workflows
83
+
84
+ def find_by_uuid(self, uuid):
85
+ for stype in self.script_dict:
86
+ for action in self.script_dict[stype]:
87
+
88
+ if action['uuid'] == int(uuid):
89
+ return action
90
+
91
+ def _convert_type(self, args, arg_types):
92
+ if type(arg_types) is not list:
93
+ arg_types = [arg_types]
94
+ for arg_type in arg_types:
95
+ try:
96
+ args = eval(f"{arg_type}('{args}')")
97
+ return
98
+ except Exception:
99
+ pass
100
+ raise TypeError(f"Input type error: cannot convert '{args}' to {arg_type}.")
101
+
102
+ def update_by_uuid(self, uuid, args, output):
103
+ bool_dict = {"True": True, "False": False}
104
+ action = self.find_by_uuid(uuid)
105
+ if type(action['args']) is dict:
106
+ for arg in action['args']:
107
+ if not args[arg].startswith("#"):
108
+
109
+ if args[arg] in bool_dict.keys():
110
+ args[arg] = bool_dict[args[arg]]
111
+ elif args[arg] == "None" or args[arg] == "":
112
+ args[arg] = None
113
+ else:
114
+ if arg in action['arg_types']:
115
+ arg_types = action['arg_types'][arg]
116
+ self._convert_type(args[arg], arg_types)
117
+ else:
118
+ try:
119
+ args[arg] = eval(args[arg])
120
+ except Exception:
121
+ pass
122
+ else:
123
+ args = list(args.values())[0]
124
+ if not args.startswith("#"):
125
+ if args in bool_dict.keys():
126
+ args = bool_dict[args]
127
+
128
+ else:
129
+ if 'arg_types' in action:
130
+ arg_types = action['arg_types']
131
+ self._convert_type(args, arg_types)
132
+
133
+ # print(args)
134
+ action['args'] = args
135
+ # print(action)
136
+ action['return'] = output
137
+
138
+ @property
139
+ def stypes(self):
140
+ return list(self.script_dict.keys())
141
+
142
+ @property
143
+ def currently_editing_script(self):
144
+ return self.script_dict[self.editing_type]
145
+
146
+ @currently_editing_script.setter
147
+ def currently_editing_script(self, script):
148
+ self.script_dict[self.editing_type] = script
149
+
150
+ @property
151
+ def currently_editing_order(self):
152
+ return self.id_order[self.editing_type]
153
+
154
+ @currently_editing_order.setter
155
+ def currently_editing_order(self, script):
156
+ self.id_order[self.editing_type] = script
157
+
158
+ # @property
159
+ # def editing_type(self):
160
+ # return self.editing_type
161
+
162
+ # @editing_type.setter
163
+ # def editing_type(self, change_type):
164
+ # self.editing_type = change_type
165
+
166
+ def update_time_stamp(self):
167
+ self.last_modified = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
168
+
169
+ def get_script(self, stype: str):
170
+ return self.script_dict[stype]
171
+
172
+ def isEmpty(self) -> bool:
173
+ if not (self.script_dict['script'] or self.script_dict['prep'] or self.script_dict['cleanup']):
174
+ return True
175
+ return False
176
+
177
+ def _sort(self, script_type):
178
+ if len(self.id_order[script_type]) > 0:
179
+ for action in self.script_dict[script_type]:
180
+ for i in range(len(self.id_order[script_type])):
181
+ if action['id'] == int(self.id_order[script_type][i]):
182
+ # print(i+1)
183
+ action['id'] = i + 1
184
+ break
185
+ self.id_order[script_type].sort()
186
+ if not int(self.id_order[script_type][-1]) == len(self.script_dict[script_type]):
187
+ new_order = list(range(1, len(self.script_dict[script_type]) + 1))
188
+ self.id_order[script_type] = [str(i) for i in new_order]
189
+ self.script_dict[script_type].sort(key=lambda x: x['id'])
190
+
191
+ def sort_actions(self, script_type=None):
192
+ if script_type:
193
+ self._sort(script_type)
194
+ else:
195
+ for i in self.stypes:
196
+ self._sort(i)
197
+
198
+ def add_action(self, action: dict):
199
+ current_len = len(self.currently_editing_script)
200
+ action_to_add = action.copy()
201
+ action_to_add['id'] = current_len + 1
202
+ action_to_add['uuid'] = uuid.uuid4().fields[-1]
203
+ self.currently_editing_script.append(action_to_add)
204
+ self.currently_editing_order.append(str(current_len + 1))
205
+ self.update_time_stamp()
206
+
207
+ def add_variable(self, statement, variable):
208
+ current_len = len(self.currently_editing_script)
209
+ uid = uuid.uuid4().fields[-1]
210
+ action_list = [{"id": current_len + 1, "instrument": 'variable', "action": variable,
211
+ "args": 'None' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''}]
212
+ self.currently_editing_script.extend(action_list)
213
+ self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
214
+ self.update_time_stamp()
215
+
216
+ def add_logic_action(self, logic_type: str, statement):
217
+ current_len = len(self.currently_editing_script)
218
+ uid = uuid.uuid4().fields[-1]
219
+ logic_dict = {
220
+ "if":
221
+ [
222
+ {"id": current_len + 1, "instrument": 'if', "action": 'if',
223
+ "args": 'True' if statement == '' else statement,
224
+ "return": '', "uuid": uid, "arg_types": ''},
225
+ {"id": current_len + 2, "instrument": 'if', "action": 'else', "args": '', "return": '',
226
+ "uuid": uid},
227
+ {"id": current_len + 3, "instrument": 'if', "action": 'endif', "args": '', "return": '',
228
+ "uuid": uid},
229
+ ],
230
+ "while":
231
+ [
232
+ {"id": current_len + 1, "instrument": 'while', "action": 'while',
233
+ "args": 'False' if statement == '' else statement, "return": '', "uuid": uid, "arg_types": ''},
234
+ {"id": current_len + 2, "instrument": 'while', "action": 'endwhile', "args": '', "return": '',
235
+ "uuid": uid},
236
+ ],
237
+
238
+ "wait":
239
+ [
240
+ {"id": current_len + 1, "instrument": 'wait', "action": "wait",
241
+ "args": '0' if statement == '' else statement,
242
+ "return": '', "uuid": uid, "arg_types": "float"},
243
+ ],
244
+ }
245
+ action_list = logic_dict[logic_type]
246
+ self.currently_editing_script.extend(action_list)
247
+ self.currently_editing_order.extend([str(current_len + i + 1) for i in range(len(action_list))])
248
+ self.update_time_stamp()
249
+
250
+ def delete_action(self, id: int):
251
+
252
+ uid = next((action['uuid'] for action in self.currently_editing_script if action['id'] == int(id)), None)
253
+ id_to_be_removed = [action['id'] for action in self.currently_editing_script if action['uuid'] == uid]
254
+ order = self.currently_editing_order
255
+ script = self.currently_editing_script
256
+ self.currently_editing_order = [i for i in order if int(i) not in id_to_be_removed]
257
+ self.currently_editing_script = [action for action in script if action['id'] not in id_to_be_removed]
258
+ self.sort_actions()
259
+ self.update_time_stamp()
260
+
261
+ def duplicate_action(self, id: int):
262
+ action_to_duplicate = next((action for action in self.currently_editing_script if action['id'] == int(id)), None)
263
+ insert_id = action_to_duplicate.get("id")
264
+ self.add_action(action_to_duplicate)
265
+ # print(self.currently_editing_script)
266
+ if action_to_duplicate is not None:
267
+ # Update IDs for all subsequent actions
268
+ for action in self.currently_editing_script:
269
+ if action['id'] > insert_id:
270
+ action['id'] += 1
271
+ self.currently_editing_script[-1]['id'] = insert_id + 1
272
+ # Sort actions if necessary and update the time stamp
273
+ self.sort_actions()
274
+ self.update_time_stamp()
275
+ else:
276
+ raise ValueError("Action not found: Unable to duplicate the action with ID", id)
277
+
278
+ def config(self, stype):
279
+ """
280
+ take the global script_dict
281
+ :return: list of variable that require input
282
+ """
283
+ configure = []
284
+ config_type_dict = {}
285
+ for action in self.script_dict[stype]:
286
+ args = action['args']
287
+ if args is not None:
288
+ if type(args) is not dict:
289
+ if type(args) is str and args.startswith("#") and not args[1:] in configure:
290
+ configure.append(args[1:])
291
+ config_type_dict[args[1:]] = action['arg_types']
292
+
293
+ else:
294
+ for arg in args:
295
+ if type(args[arg]) is str \
296
+ and args[arg].startswith("#") \
297
+ and not args[arg][1:] in configure:
298
+ configure.append(args[arg][1:])
299
+ if arg in action['arg_types']:
300
+ if action['arg_types'][arg] == '':
301
+ config_type_dict[args[arg][1:]] = "any"
302
+ else:
303
+ config_type_dict[args[arg][1:]] = action['arg_types'][arg]
304
+ else:
305
+ config_type_dict[args[arg][1:]] = "any"
306
+ # todo
307
+ return configure, config_type_dict
308
+
309
+ def config_return(self):
310
+ """
311
+ take the global script_dict
312
+ :return: list of variable that require input
313
+ """
314
+
315
+ return_list = [action['return'] for action in self.script_dict['script'] if not action['return'] == '']
316
+ output_str = "return {"
317
+ for i in return_list:
318
+ output_str += "'" + i + "':" + i + ","
319
+ output_str += "}"
320
+ return output_str, return_list
321
+
322
+ def finalize(self):
323
+ self.status = "finalized"
324
+ self.update_time_stamp()
325
+
326
+ def save_as(self, name):
327
+ self.name = name
328
+ self.status = "editing"
329
+ self.update_time_stamp()
330
+
331
+ def indent(self, unit=0):
332
+ string = "\n"
333
+ for _ in range(unit):
334
+ string += "\t"
335
+ return string
336
+
337
+ def compile(self, script_path=None):
338
+ """
339
+ Compile the current script to a Python file.
340
+ :return: String to write to a Python file.
341
+ """
342
+ self.sort_actions()
343
+ run_name = self.name if self.name else "untitled"
344
+ run_name = self.validate_function_name(run_name)
345
+ exec_string = ''
346
+
347
+ for i in self.stypes:
348
+ exec_string += self._generate_function_header(run_name, i)
349
+ exec_string += self._generate_function_body(i)
350
+
351
+ if script_path:
352
+ self._write_to_file(script_path, run_name, exec_string)
353
+
354
+ return exec_string
355
+
356
+ @staticmethod
357
+ def validate_function_name(name):
358
+ """Replace invalid characters with underscores"""
359
+ name = re.sub(r'\W|^(?=\d)', '_', name)
360
+ # Check if it's a Python keyword and adjust if necessary
361
+ if keyword.iskeyword(name):
362
+ name += '_'
363
+ return name
364
+
365
+ def _generate_function_header(self, run_name, stype):
366
+ """
367
+ Generate the function header.
368
+ """
369
+ configure, _ = self.config(stype)
370
+ function_header = f"\n\ndef {run_name}_{stype}("
371
+
372
+ if stype == "script":
373
+ function_header += ",".join(configure)
374
+
375
+ function_header += "):"
376
+ function_header += self.indent(1) + f"global {run_name}_{stype}"
377
+ return function_header
378
+
379
+ def _generate_function_body(self, stype):
380
+ """
381
+ Generate the function body for each type in stypes.
382
+ """
383
+ body = ''
384
+ indent_unit = 1
385
+
386
+ for index, action in enumerate(self.script_dict[stype]):
387
+ text, indent_unit = self._process_action(indent_unit, action, index, stype)
388
+ body += text
389
+ return_str, return_list = self.config_return()
390
+ if return_list and stype == "script":
391
+ body += self.indent(indent_unit) + return_str
392
+
393
+ return body
394
+
395
+ def _process_action(self, indent_unit, action, index, stype):
396
+ """
397
+ Process each action within the script dictionary.
398
+ """
399
+ instrument = action['instrument']
400
+ args = self._process_args(action['args'])
401
+ save_data = action['return']
402
+ action_name = action['action']
403
+ next_action = self._get_next_action(stype, index)
404
+ if instrument == 'if':
405
+ return self._process_if(indent_unit, action_name, args, next_action)
406
+ elif instrument == 'while':
407
+ return self._process_while(indent_unit, action_name, args, next_action)
408
+ elif instrument == 'variable':
409
+ return self.indent(indent_unit) + f"{action_name} = {args}", indent_unit
410
+ elif instrument == 'wait':
411
+ return f"{self.indent(indent_unit)}time.sleep({args})", indent_unit
412
+ else:
413
+ return self._process_instrument_action(indent_unit, instrument, action_name, args, save_data)
414
+
415
+ def _process_args(self, args):
416
+ """
417
+ Process arguments, handling any specific formatting needs.
418
+ """
419
+ if isinstance(args, str) and args.startswith("#"):
420
+ return args[1:]
421
+ return args
422
+
423
+ def _process_if(self, indent_unit, action, args, next_action):
424
+ """
425
+ Process 'if' and 'else' actions.
426
+ """
427
+ exec_string = ""
428
+ if action == 'if':
429
+ exec_string += self.indent(indent_unit) + f"if {args}:"
430
+ indent_unit += 1
431
+ if next_action and next_action['instrument'] == 'if' and next_action['action'] == 'else':
432
+ exec_string += self.indent(indent_unit) + "pass"
433
+ # else:
434
+
435
+ elif action == 'else':
436
+ indent_unit -= 1
437
+ exec_string += self.indent(indent_unit) + "else:"
438
+ indent_unit += 1
439
+ if next_action and next_action['instrument'] == 'if' and next_action['action'] == 'endif':
440
+ exec_string += self.indent(indent_unit) + "pass"
441
+ else:
442
+ indent_unit -= 1
443
+ return exec_string, indent_unit
444
+
445
+ def _process_while(self, indent_unit, action, args, next_action):
446
+ """
447
+ Process 'while' and 'endwhile' actions.
448
+ """
449
+ exec_string = ""
450
+ if action == 'while':
451
+ exec_string += self.indent(indent_unit) + f"while {args}:"
452
+ indent_unit += 1
453
+ if next_action and next_action['instrument'] == 'while':
454
+ exec_string += self.indent(indent_unit) + "pass"
455
+ elif action == 'endwhile':
456
+ indent_unit -= 1
457
+ return exec_string, indent_unit
458
+
459
+ def _process_instrument_action(self, indent_unit, instrument, action, args, save_data):
460
+ """
461
+ Process actions related to instruments.
462
+ """
463
+ if isinstance(args, dict):
464
+ args_str = self._process_dict_args(args)
465
+ single_line = f"{instrument}.{action}(**{args_str})"
466
+ elif isinstance(args, str):
467
+ single_line = f"{instrument}.{action} = {args}"
468
+ else:
469
+ single_line = f"{instrument}.{action}()"
470
+
471
+ if save_data:
472
+ save_data += " = "
473
+
474
+ return self.indent(indent_unit) + save_data + single_line, indent_unit
475
+
476
+ def _process_dict_args(self, args):
477
+ """
478
+ Process dictionary arguments, handling special cases like variables.
479
+ """
480
+ args_str = args.__str__()
481
+ for arg in args:
482
+ if isinstance(args[arg], str) and args[arg].startswith("#"):
483
+ args_str = args_str.replace(f"'#{args[arg][1:]}'", args[arg][1:])
484
+ elif self._is_variable(arg):
485
+ args_str = args_str.replace(f"'{args[arg]}'", args[arg])
486
+ return args_str
487
+
488
+ def _get_next_action(self, stype, index):
489
+ """
490
+ Get the next action in the sequence if it exists.
491
+ """
492
+ if index < (len(self.script_dict[stype]) - 1):
493
+ return self.script_dict[stype][index + 1]
494
+ return None
495
+
496
+ def _is_variable(self, arg):
497
+ """
498
+ Check if the argument is of type 'variable'.
499
+ """
500
+ return arg in self.script_dict and self.script_dict[arg].get("arg_types") == "variable"
501
+
502
+ def _write_to_file(self, script_path, run_name, exec_string):
503
+ """
504
+ Write the compiled script to a file.
505
+ """
506
+ with open(script_path + run_name + ".py", "w") as s:
507
+ if self.deck:
508
+ s.write(f"import {self.deck} as deck")
509
+ else:
510
+ s.write("deck = None")
511
+ s.write("\nimport time")
512
+ s.write(exec_string)
513
+
514
+
515
+ if __name__ == "__main__":
516
+ a = Script()
517
+
518
+ print("")