def update_waiting(self): """ Update function, runs if state is WAITING. Function takes value from description (desc_value). If this value is a keyword ($vars and utils.program_values) then it generates corresponding output. Otherwise function takes value from input. After preparations function prints value and sets state to ACTIVE. """ value = self.desc_value if value == "$vars": variables = [] for var in self.variables.items(): if var[0].startswith(f"{self.scope}$"): variables.append((var[0][len(f"{self.scope}$"):], var[1])) value = dict(variables) elif utils.program_values(value, True): value = utils.program_values(value) else: value = self.get_value(0) value = value if value is not None else self.desc_value logger.log_message(value) if base.Node.ut_catch is not None: check_value = base.Node.ut_catch.pop(0) if check_value is not None and check_value != value: logger.log_error(f"expected value '{check_value}'") self.state = INACTIVE
def update_waiting(self): """ Update function, runs if state is WAITING. Log error if function runs (logger will raise exception). """ logger.log_error(f"node 'error' raised exception '{self.desc_value}'")
def update_waiting(self): """ Update function, runs if state is WAITING. Prompt is value from node description (desc_value). Function tries to find construction of type {<var name>$<type>} in prompt. If it was found then function tries to generate string version of value from variable. If description wasn't changed then function takes '>>> ' (default) as prompt otherwise desc_value. After taking value from input, function saves it to output and changes state to ACTIVE. """ pattern = r"{(?P<var>[^\{\}]+?)(?:\$(?P<type>[^\{\}]+?))?}" self.desc_value = saxutils.unescape(self.desc_value) # TODO: optimize match = list(re.finditer(pattern, self.desc_value)) while match: match = match[0] var_name = f"{self.scope}$" + match["var"] var_type = match["type"] if var_type is not None: if var_type in utils.types_default: var_type = utils.types_default[var_type].__class__ else: var_type = utils.coercion if var_name in self.variables: var_value = self.variables[var_name] if var_type is not None: var_value = var_type(var_value) self.desc_value = list(self.desc_value) self.desc_value[match.span()[0]:match.span()[1]] = list( str(var_value)) self.desc_value = "".join(self.desc_value) match = list(re.finditer(pattern, self.desc_value)) prompt = ">>> " if self.desc_value: if self.desc_value.endswith(" "): prompt = self.desc_value else: prompt = self.desc_value + " " if base.Node.ut_send is not None: if not base.Node.ut_send: logger.log_error("unit tests are empty") value = base.Node.ut_send.pop(0) value = logger.Color.colored_input(prompt, value=value) else: value = logger.Color.colored_input(prompt) self.set_value(utils.coercion(value), 0) self.state = ACTIVE
def update_waiting(self): """ Update function, runs if state is WAITING. If node has both input values then function checks zero division. If divider not equal to zero then function evaluates output value and changes state otherwise function raises error. """ if self.get_value(0) is not None and self.get_value(1) is not None: if self.get_value(1) == 0: logger.log_error(f"node 'div' raised exception 'Zero Division'") self.set_value(self.get_value(0) // self.get_value(1), 0) self.state = ACTIVE
def update_waiting(self): """ Update function, runs if state is WAITING. Function takes name of value from node description (desc_value) and generates name of variable based on node scope. If key or value types are not acceptable then function raises errors. Possible types are in utils.types. Variable of type 'dict' has next description: structure - name of structure (dict); key_type - type of all keys in structure; value_type - type of all value in structure; values - dict of values in structure. """ key_type = self.get_value(0) if key_type not in utils.types and key_type is not None: key_type = type(key_type) if key_type not in utils.types: logger.log_error( f"unaccepted type for key in node '{self.name}/{self.id}' " f"with name '{self.desc_value}'") value_type = self.get_value(1) if value_type not in utils.types and value_type is not None: value_type = type(value_type) if value_type not in utils.types: logger.log_error( f"unaccepted type for value in node '{self.name}/{self.id}' " f"with name '{self.desc_value}'") desc_value = f"{self.scope}$" + self.desc_value if key_type is not None and value_type is not None: self.struct_variables[desc_value] = { "structure": "dict", "key_type": key_type, "value_type": value_type, "values": {} } self.state = ACTIVE
def run(n, w, s, limit=10**5): """ Function iteratively launches nodes update functions for different states: INACTIVE, WAITING and ACTIVE. If node stop or similar generates exception StopSync, then function stops iteration, otherwise if the iteration exceeds the limit function stops updating. :param n: list of nodes information :param w: list of wires information :param s: list of scope information :param limit: limit number of updates """ create_structure(n, w, s) if "run" not in map(lambda x: x.name, base.Node.nodes.values()) and \ "unit test" not in map(lambda x: x.name, base.Node.nodes.values()): logger.log_error("no 'start' node") logger.log_success("program started") for utils.iteration in range(limit): try: nodes = list(base.Node.nodes.values()) random.shuffle(nodes) for node in nodes: node.update(base.INACTIVE) for node in nodes: node.update(base.WAITING) for node in nodes: node.update(base.ACTIVE) except exceptions.StopSync: logger.log_success("program stopped via node 'stop'") utils.iteration = -1 break if utils.iteration != -1: active_nodes = [] for node in base.Node.nodes.values(): if node.state == base.WAITING: active_nodes.append((node.name, node.id)) if active_nodes: active_nodes = ',\n\t'.join( map(lambda x: f"{x[0]} (id: {x[1]})", active_nodes)) active_nodes = f"\nActive nodes:[\n\t{active_nodes}\n]" else: active_nodes = "" logger.log_error(f"iteration overstepped the limit{active_nodes}") if base.Node.ut_send: logger.log_error("value input expected") elif base.Node.ut_catch: logger.log_error(f"expected value '{base.Node.ut_catch}'")
def __init__(self, data): """ Wire constructor. Attribute takes dictionary with several descriptive parameters: source - identifier of the first connected object in scheme; target - identifier of the second connected object in scheme; exitX/exitY - connection (exit) point in source object; entryX/entryY - connection (entry) point in target object; It checks validity of connected objects and calculates entry and exit connector indexes :param dict data: information about wire """ self.wires.append(self) self.raw_data = data self.source = Node.nodes.get(data["source"], None) self.target = Node.nodes.get(data["target"], None) if self.source is None and self.target is None: logger.log_warning("no source and target nodes found") elif self.source is None: logger.log_error( f"no source node found for wire with target '{self.target.name}/{self.target.id}'" ) elif self.target is None: logger.log_error( f"no target node found for wire with source '{self.source.name}/{self.source.id}'" ) self.exit = data["exitX"], data["exitY"] self.entry = data["entryX"], data["entryY"] if self.exit[0] == self.entry[0]: logger.log_error( f"wrong wire connection from node '{self.source.name}/{self.source.id}' " f"to node '{self.target.name}/{self.target.id}'") if self.exit[0] == 0: self.exit, self.entry = self.entry, self.exit self.target, self.source = self.source, self.target self.entry_connector = self.target.input_connectors.index( self.entry[1]) self.exit_connector = self.source.output_connectors.index(self.exit[1])
def update_connections(self): """ Function generates information about the connections of node inputs and inputs on the diagram. Checks whether the connection is correct. Creates relations between nodes, this requires working with the class Wire. """ for i in range(len(self.inputs)): wires = Wire.get_nodes_from_input(self, i) self.inputs[i] = wires offset = 0 ins = [] if str(self.name).startswith("function"): inputs_info = self.raw_data["inputs_names"] outputs_info = self.raw_data["outputs_names"] else: inputs_info = nodes_info.nodes_info[self.name]["inputs"] outputs_info = nodes_info.nodes_info[self.name]["outputs"] self.actual_inputs = {} for i, v in enumerate(inputs_info): variant = v[0] other = v[1:] if variant == SINGLE or SMALL in other: ins.append(sum(self.inputs[offset:offset + 1], [])) self.actual_inputs[i] = tuple(range(offset, offset + 1)) offset += 1 elif variant == MULTIPLE: if DIRECTED not in other: ins.append(sum(self.inputs[offset:offset + 7], [])) self.actual_inputs[i] = tuple(range(offset, offset + 7)) offset += 7 else: ins.append(sum(self.inputs[offset:offset + 5], [])) self.actual_inputs[i] = tuple(range(offset, offset + 5)) offset += 5 self.actual_outputs = {} offset = 0 for i, v in enumerate(outputs_info): variant = v[0] other = v[1:] if variant == SINGLE or SMALL in other: self.actual_outputs[i] = tuple(range(offset, offset + 1)) offset += 1 elif variant == MULTIPLE: if DIRECTED not in other: self.actual_outputs[i] = tuple(range(offset, offset + 7)) offset += 7 else: self.actual_outputs[i] = tuple(range(offset, offset + 5)) offset += 5 for i, v in enumerate(ins): variant = inputs_info[i][0] if variant == SINGLE and len(v) > 1: logger.log_error( f"wrong input connections count in node '{self.name}/{self.id}'" ) self.inputs = ins for i in range(len(self.outputs)): self.outputs[i] = Wire.get_nodes_from_output(self, i) offset = 0 outs = [] self.output_values = [] for i in outputs_info: variant = i[0] other = i[1:] if variant == SINGLE or SMALL in other: outs.append(sum(self.outputs[offset:offset + 1], [])) offset += 1 elif variant == MULTIPLE: if DIRECTED not in other: outs.append(sum(self.outputs[offset:offset + 7], [])) offset += 7 else: outs.append(sum(self.outputs[offset:offset + 5], [])) offset += 5 self.outputs = outs self.output_values = [] for _ in self.outputs: self.output_values.append(None)
def parse(file_path): """ Function parse uncompressed .drawio file and returns all nodes and all connected wires with description :param file_path: path to .drawio file :return: tuple (nodes, wires, scopes) """ for alias, node_class in loader.node_aliases.items(): if alias not in nodes_info.nodes_info: nodes_info.nodes_info.update({alias: node_class.desc}) if os.path.exists(file_path): with open(file_path, "r", encoding="utf-8") as file: data = unescape("".join(file.read().split("\n"))) data = "'".join(data.split("'")) elif not file_path: logger.log_error(f"no file to run") else: logger.log_error(f"file with name \"{file_path}\" not found") res = "" remove_data = [] for diag in re.finditer(diagram_pattern, data): remove_data.append( (diag['diagram'], coder.from_library(diag['diagram']))) res += coder.from_library(diag['diagram']) return_data = copy(data) for d in remove_data: return_data = return_data.replace(d[0], d[1]) data = res if not data: logger.log_error("no data in diagram") nodes = [] wires = [] for node in re.finditer(node_pattern, data): patterns = { "id": id_pattern, "img": image_pattern, "value": value_pattern, "x": x_pattern, "y": y_pattern, "width": width_pattern, "height": height_pattern, "node_name": node_name_pattern, "fixed_scope": fixed_scope_pattern } node = node.group() if not str(re.search(patterns["node_name"], node).group("node_name")).startswith("function"): for key in patterns.keys(): res = re.search(patterns[key], node) if key == "value": patterns[key] = res.group("value0") if res.group( "value0") is not None else res.group("value1") else: if res: patterns[key] = res.group(key) else: patterns[key] = None if patterns["node_name"] not in nodes_info.nodes_info: logger.log_error( f"no built-in nodes found with name '{patterns['node_name']}'" ) inputs = get_connectors( nodes_info.nodes_info[patterns["node_name"]]["inputs"]) outputs = get_connectors( nodes_info.nodes_info[patterns["node_name"]]["outputs"]) ratio = (1, max( get_ratio(nodes_info.nodes_info[patterns["node_name"]] ["inputs"]), get_ratio(nodes_info.nodes_info[patterns["node_name"]] ["outputs"]))) inputs = list(map(lambda x: round(x / ratio[1], 5), inputs)) outputs = list(map(lambda x: round(x / ratio[1], 5), outputs)) patterns["inputs"] = inputs patterns["outputs"] = outputs nodes.append(patterns) else: patterns["points"] = points_pattern patterns["inputs"] = inputs_pattern patterns["outputs"] = outputs_pattern for key in patterns.keys(): res = re.search(patterns[key], node) if res: if key == "value": patterns[key] = res.group("value0") if res.group( "value0") else res.group("value1") else: patterns[key] = res.group(key) if key in ("points", "inputs", "outputs"): patterns[key] = eval(res.group(key)) else: patterns[key] = None if key in ("points", "inputs", "outputs"): patterns[key] = [] inputs = get_connectors(patterns["inputs"]) outputs = get_connectors(patterns["outputs"]) ratio = (1, max(get_ratio(patterns["inputs"]), get_ratio(patterns["outputs"]))) if patterns["node_name"].startswith("function input") or \ patterns["node_name"].startswith("function output"): ratio = (ratio[0], max(2, ratio[1])) inputs = list(map(lambda x: round(x / ratio[1], 5), inputs)) outputs = list(map(lambda x: round(x / ratio[1], 5), outputs)) patterns["inputs_names"] = patterns["inputs"] patterns["inputs"] = inputs patterns["outputs_names"] = patterns["outputs"] patterns["outputs"] = outputs nodes.append(patterns) for wire in re.findall(wire_pattern, data): patterns = { "id": id_pattern, "exitX": exitX_pattern, "exitY": exitY_pattern, "entryX": entryX_pattern, "entryY": entryY_pattern, "source": source_pattern, "target": target_pattern, } for key in patterns.keys(): res = re.search(patterns[key], wire) if res: patterns[key] = res.group(key) else: patterns[key] = None nodes_ids = dict(map(lambda x: (x["id"], x["node_name"]), nodes)) connected = True for key in ["exitY", "entryY"]: if patterns[key] is None: if patterns['source'] in nodes_ids: logger.log_error( f"wrong connected wire found from node " f"'{nodes_ids[patterns['source']]}/{patterns['source']}'" ) elif patterns['target'] in nodes_ids: logger.log_error( f"wrong connected wire found to node " f"'{nodes_ids[patterns['target']]}/{patterns['target']}'" ) else: logger.log_error(f"unconnected wire found") connected = False if connected: patterns[key] = round(float(patterns[key]), 5) for key in ["exitX", "entryX"]: if patterns[key] is not None: patterns[key] = round(float(patterns[key]), 5) if connected: wires.append(patterns) scopes = [] for scope in re.findall(scope_pattern, data): patterns = { "id": id_pattern, "value": value_pattern, "x": x_pattern, "y": y_pattern, "width": width_pattern, "height": height_pattern, } for key in patterns.keys(): res = re.search(patterns[key], scope) if res: patterns[key] = res.group(key) else: patterns[key] = None scopes.append(patterns) with open(file_path, "w", encoding="utf-8") as file: file.write(return_data) return nodes, wires, scopes