def edit_connect(self, port_a, port_b, new_port_a=None, new_port_b=None): """edit_connect finds all connect clauses that match the pattern connect(<port_a>, <port_b>), in that order. If a port is an asterisk, '*', then it matches any identifier. :param port_a: string, identifier for first port; an asterisk matches all :param port_b: string, identifier for second port; an asterisk matches all :param new_port_a: string | None, replacement for port a; if None no changes are made :param new_port_b: string | None, replacement for port b; if None no changes are made """ # verify the paramaters are sensible if (port_a == '*' and new_port_a is not None) or (port_b == '*' and new_port_b is not None): raise Exception( 'Invalid to have a port match a wildcard and replace it (might result in duplicate clauses)' ) # make up to two transformations (one for each replacement) if new_port_a is not None: selector = (ConnectClauseSelector(port_a, port_b).chain( NthChildSelector(2))) self.add( SimpleTransformation(selector, Edit.make_replace(new_port_a))) if new_port_b is not None: selector = (ConnectClauseSelector(port_a, port_b).chain( NthChildSelector(4))) self.add( SimpleTransformation(selector, Edit.make_replace(new_port_b)))
def test_make_insert_before(self): # Setup insert = Edit.make_insert('Dog', insert_after=False) # Act edit = insert({'start': 0, 'stop': 2}) result = Edit.apply_edits([edit], self.data) # Assert self.assertEqual('DogCatBat123', result)
def test_make_replace(self): # Setup replace = Edit.make_replace('Dog') # Act edit = replace({'start': 0, 'stop': 2}) result = Edit.apply_edits([edit], self.data) # Assert self.assertEqual('DogBat123', result)
def set_name(self, name): """sets the model's name :param name: string """ selector = ModelIdentifierSelector() self.add(SimpleTransformation(selector, Edit.make_replace(name)))
def build_edits(self, tree, parser): # try to find the model annotation model_annotation_xpath = 'stored_definition/class_definition/class_specifier/long_class_specifier/composition/model_annotation' model_annotation_node = XPath.XPath.findAll(tree, model_annotation_xpath, parser) if not model_annotation_node: # insert the model annotation along with the modifications selector = (EquationSectionSelector().chain( NthChildSelector(-1)).assert_count( 1, 'Failed to find end of the equation section')) edit = Edit.make_insert( f'\n{config.INDENTATION}annotation({build_modifications(self.modifications, indented=False)});' ) return SimpleTransformation(selector, edit).build_edits(tree, parser) # model annotation exists, recursively update or insert the modifications model_annotation_node = model_annotation_node[0] return make_edits_for_modifications( model_annotation_node.annotation().class_modification(), self.modifications, parser, indented=config.INDENT_INSERTED_ANNOTATION_ARGS, )
def remove_connect(self, port_a, port_b): """remove_connect finds and removes the connect clause that matches :param port_a: string, first port identifier; an asterisk matches all :param port_b: string, second port identifier; an asterisk matches all """ # select the parent of the component to also select the semicolon and comments selector = (ConnectClauseSelector(port_a, port_b).chain(ParentSelector())) self.add(SimpleTransformation(selector, Edit.make_delete()))
def set_within_statement(self, within_string): """changes 'within <string>;' at the beginning of the file :param within_string: string, new value """ selector = (WithinSelector().assert_count( 1, 'A single within statement must already exist')) self.add( SimpleTransformation( selector, Edit.make_replace(f'within {within_string};')))
def transformation(self): """transformation creates the transformation required for inserting the built for loop :return: Transformation """ # select the last child of the equation section and insert after it selector = (EquationSectionSelector().chain( NthChildSelector(-1)).assert_count( 1, 'Failed to find end of the equation section')) edit = Edit.make_insert(self.build(), insert_after=True) return SimpleTransformation(selector, edit)
def remove_component_argument(self, type_, identifier, argument_name): """Remove the argument from a component :param type_: string, type of the component :param identifier: string, component identifier :param argument_name: string, name of the argument that will be removed """ if type_ is None and identifier is None: raise Exception('At least one of the parameters must not be None') selector = (ComponentDeclarationSelector(type_, identifier).chain( ComponentArgumentSelector(argument_name))) self.add(SimpleTransformation(selector, Edit.make_delete()))
def overwrite_component_redeclaration(self, type_, identifier, new_declaration): """ Overwrite the component redeclaration with a new string :param type_: string, type of the component :param identifier: string, component identifier :param new_declaration: string, new component redeclaration string. It is the entire string, i.e., argument=value """ selector = (ComponentDeclarationSelector(type_, identifier).chain( ComponentRedeclarationSelector())) self.add( SimpleTransformation(selector, Edit.make_replace(f'{new_declaration}')))
def rename_component_argument(self, type_, identifier, old_argument_name, new_argument_name): """Rename the argument name of a component :param type_: string, type of the component :param identifier: string, component identifier :param old_argument_name: string, name of the argument that will be replaced :param new_argument_name: string, name of the new argument name """ selector = (ComponentDeclarationSelector(type_, identifier).chain( ComponentModificationNameSelector(old_argument_name))) self.add( SimpleTransformation(selector, Edit.make_replace(f'{new_argument_name}')))
def execute(self): """execute applies transformations to a file and returns the result as a string :return: string, transformed source """ all_edits = [] # apply selectors and build edits for transformation in self._transformations: all_edits += transformation.build_edits(self._tree, self._parser) # apply the edits # Reading with newline='' lets us retain the original newline characters # allowing us to handle files made on Windows with \r\n with open(self._source, 'rt', newline='') as f: return Edit.apply_edits(all_edits, f.read())
def remove_component(self, type_=None, identifier=None): """remove_component removes a component declaration. Note that if the component is part of a list of declarations, e.g. TypeName IdentifierA, IdentifierB, IdentifierC; then _all_ declarations are removed. :param type_: string, optional, type in the declaration :param identifier: string, optional, identifier in the declaration """ if type_ is None and identifier is None: raise Exception('At least one of the parameters must not be None') selector = ( ComponentDeclarationSelector(type_, identifier).chain( ParentSelector()) # component_list .chain(ParentSelector()) # component_clause .chain(ParentSelector())) # element self.add(SimpleTransformation(selector, Edit.make_delete()))
def update_component_modification(self, type_, identifier, modification_name, new_value, if_value=None): """update_component_modification changes the value of an _existing_ component modification value. ie this won't work if the argument isn't already used :param type_: string, component type :param identifier: string, component identifier :param modification_name: string, modification to update :param new_value: string, new modification value :param if_value: string, if provided it will only update the value if the existing value matches this """ selector = (ComponentDeclarationSelector(type_, identifier).chain( ComponentModificationValueSelector(modification_name, modification_value=if_value))) self.add(SimpleTransformation(selector, Edit.make_replace(new_value)))
def transformation(self): """transformation creates the transformation required for inserting the built component :return: Transformation """ if self._insert_index == 0: selector = (ElementListSelector().chain(NthChildSelector(0))) insert_after = False elif self._insert_index < 0: # insert after the last child selector = (ElementListSelector().chain(NthChildSelector(-1))) insert_after = True else: selector = (ElementListSelector().chain( NthChildSelector(self._insert_index))) insert_after = True edit = Edit.make_insert(self.build(), insert_after=insert_after) return SimpleTransformation(selector, edit)
def make_edits_for_modifications(class_modification_node, modifications, parser, depth=1, indented=False): """Constructs a list of edits required to update the node with the provided modifications. :param class_modification_node: modelicaParser.Class_modificationContext :param modifications: dict :param parser: antlr4.Parser :param depth: int, current modification depth, used for determining code indentation :param indent: bool, if true each inserted modification will be indented on new line """ requested_modifications = deepcopy(modifications) overwrite_modifications = requested_modifications.pop( 'OVERWRITE_MODIFICATIONS', False) if overwrite_modifications: # don't care about selectively updating existing values # just overwrite any existing modifications new_modifications_string = build_modifications(requested_modifications, depth=depth, indented=indented) edit = Edit.make_replace(new_modifications_string) # replace the entire argument_list with our new modifications return [edit(class_modification_node.argument_list())] all_edits = [] element_modification_xpath = 'class_modification/argument_list/argument/element_modification_or_replaceable/element_modification' element_modification_nodes = XPath.XPath.findAll( class_modification_node, element_modification_xpath, parser) # iterate through the existing element modifications for element_modification_node in element_modification_nodes: # check if there's a request to update this modification element_modification_name = element_modification_node.name().getText() if element_modification_name in requested_modifications: # found a modification to update requested_modification_value = requested_modifications.pop( element_modification_name) if isinstance(requested_modification_value, dict): # recursively make edits for this modification next_class_modification_node = element_modification_node.modification( ).class_modification() all_edits += make_edits_for_modifications( next_class_modification_node, requested_modification_value, parser, depth=depth + 1, indented=indented) else: edit = Edit.make_replace(f'={requested_modification_value}') # replace the modification node with our value all_edits.append(edit( element_modification_node.modification())) # remaining modifications will need to be inserted (matched modification were removed from the dict) if requested_modifications: new_modifications_string = build_modifications(requested_modifications, depth=depth, indented=indented) edit = Edit.make_insert(', ' + new_modifications_string, insert_after=True) all_edits.append(edit(class_modification_node.argument_list())) return all_edits