def _make_new_list(requirements): """Make a flat Python list, using parso nodes. Args: requirements (iter[:class:`parso.python.tree.String`]): The Rez requirements that'll be used to generate the new list. No nodes related to punctuation should be used here. Just requirements. Returns: :class:`parso.python.tree.PythonNode`: The generated list. """ nodes = [] for requirement in requirements: nodes.append(requirement) nodes.append(tree.Operator(",", (0, 0))) prefix = "\n" if not nodes: prefix = "" return tree.PythonNode( "atom", [tree.Operator("[", (0, 0))] + nodes + [tree.Operator("]", (0, 0), prefix=prefix)], )
def _make_intersphinx_pairs(pairs): """Convert key/value pairs into something that parso can convert into dict key/values. Args: pairs (iter[tuple[str, str or tuple[str, str or NoneType]]): The intersphinx key/value mapping. Keys are usually the name of Rez packages and each value is the URL to the API documentation that we need to link to. Returns: list[:class:`parso.python.tree.Node`]: The key/value pairs, flattened into parso objects. """ output = [] for row, (key, value) in enumerate(sorted(pairs)): if isinstance(key, six.string_types): key = '"{key}"'.format(key=key) if isinstance(value, six.string_types): value = '"{value}"'.format(value=value) key_length = len(key) value_length = len(value) output.append(tree.String(key, (row, 0), prefix="\n ")) output.append(tree.Operator(":", (row, key_length))) output.append(tree.String(str(value), (row, key_length + len(":")), prefix=" ")) output.append(tree.Operator(",", (row, key_length + len(":") + value_length))) return output
def _make_tests_node(data): """Create the final "tests" data that will be written to a package.py file. Important: This function technically doesn't create a "correct" parso dict. For the sake of simplicity, some corners were cut. For example, value data is converted into a raw :class:`parso.python.tree.String`. It's not technically "correct" but since this function is called just before writing the data into disk and not further modified, it's okay. Args: data (iter[str, :class:`parso.python.tree.PythonBaseNode`]): The key / value information that will be converted into a parso dict. Returns: :class:`parso.python.tree.PythonNode`: An atom PythonNode which has a full dictionary "tests" defined. This will later override an existing "tests" attribute in a package.py or be appended to the file as an assignment. """ nodes = [] for key, value in data: nodes.extend([ tree.String(_escape(key), (0, 0), prefix="\n "), tree.Operator(":", (0, 0)), ]) if isinstance(value, collections.MutableMapping): nodes.append(_make_dict_nodes(sorted(value.items()), prefix=" ")) elif hasattr(value, "get_code"): if hasattr(value, "prefix"): value.prefix = " " nodes.append(value) else: nodes.append(tree.String(_escape_all(value), (0, 0), prefix=" ")) nodes.append(tree.Operator(",", (0, 0))) base = tree.PythonNode("dictorsetmaker", nodes) return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0)), base, tree.Operator("}", (0, 0), prefix="\n") ], )
def make_replacement_nodes(parts, prefix): """Convert namespace tokens into parso nodes. Args: parts (list[str]): The namespace import to convert. e.g. ["foo", "bar", "etc"]. prefix (str): A string that will get placed before the joined result of `parts`. Usually, this is just a single " ". Returns: list[:class:`parso.python.tree.Name` or :class:`parso.python.tree.Operator`]: The converted nodes of `parts`. """ new = [] for index, name in enumerate(parts): new.append(tree.Name(name, (0, 0))) if index + 1 < len(parts): new.append(tree.Operator(".", (0, 0))) new[0].prefix = prefix return new
def _make_dict_nodes(data, prefix=""): """Create a parso node that represents the inner key / value pairs of a Python dict. In terms of the work needed to generate a Rez "tests" attribute, this function is used mainly to create the "inner dict" that a "tests" attribute frequently defines. Args: data (iter[tuple[str, str or list or :class:`parso.python.tree.PythonBaseNode`]]): Returns: :class:`parso.python.tree.PythonNode`: A node that defines the inside of a parso dict. This node is not equivalent to an actual dict and still needs to be wrapped with another :class:`parso.python.tree.PythonNode` to be valid. """ nodes = [] for key, item in data: item = _escape_all(item) nodes.append( tree.String(_escape(key), (0, 0), prefix="\n {prefix}".format(prefix=prefix))) nodes.append(tree.Operator(":", (0, 0))) nodes.append( tree.PythonNode( "atom", [ tree.String(item, (0, 0), prefix=" "), tree.Operator(",", (0, 0)) ], ), ) return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0), prefix=" "), tree.PythonNode("dictorsetmaker", nodes), tree.Operator("}", (0, 0), prefix="\n "), ], )
def _make_intersphinx_dict(pairs): """Create a parso literal that defines ``intersphinx_mapping``. Args: pairs (list[:class:`parso.python.tree.Node`]): The key / value dict pairs that will be added into a new ``intersphinx_mapping`` dict literal. Returns: :class:`parso.python.tree.PythonNode`: The generated dict. """ return tree.PythonNode( "atom", [ tree.Operator("{", (0, 0), prefix=" "), tree.PythonNode("dictorsetmaker", pairs), tree.Operator("}", (len(pairs), 0), prefix="\n"), ], )
def convert_leaf(self, pgen_grammar, type, value, prefix, start_pos): # print('leaf', repr(value), token.tok_name[type]) if type == NAME: if value in pgen_grammar.keywords: return tree.Keyword(value, start_pos, prefix) else: return tree.Name(value, start_pos, prefix) elif type == STRING: return tree.String(value, start_pos, prefix) elif type == NUMBER: return tree.Number(value, start_pos, prefix) elif type == NEWLINE: return tree.Newline(value, start_pos, prefix) elif type == ENDMARKER: return tree.EndMarker(value, start_pos, prefix) else: return tree.Operator(value, start_pos, prefix)
def _separate_with_commas(nodes): """Add a comma between every parso node, starting at the first element. Args: nodes (list): Some objects that will get parso nodes inserted between its elements. Returns: list[:class:`parso.python.Tree.PythonBaseNode`]: The original `nodes` plus comma operators. """ nodes = copy.deepcopy(nodes) for index in reversed(range(1, len(nodes))): nodes.insert(index, tree.Operator(",", (0, 0))) return nodes
def _get_entries(assignment): """Find every help key + value entry from some "help" attribute. If `assignment` is invalid, this function may return an empty list. Args: assignment (:class:`parso.python.tree.ExprStmt`): The node that assigns the "help" attribute, which will be modified. Returns: list[:class:`parso.python.tree.PythonNode`]: The list node that is a parso "list of list of strs". """ root = _get_list_root(assignment) if root: return _get_inner_list_entries(root) string_root = _get_str_root(assignment) if not string_root: return [] if string_root.get_code().strip() in ("''", '""'): return [] node = tree.PythonNode( "testlist_comp", [ tree.String( '"{rez_configuration.DEFAULT_HELP_LABEL}"'.format( rez_configuration=rez_configuration), (0, 0), ), tree.Operator(",", (0, 0)), string_root, ], ) for child in node.children: child.parent = node return [node]
def _make_import(namespace_parts, prefix="", alias=None): """Make a new from-import node and insert it into an existing parso graph. Args: namespace_parts (list[str]): The import that will be converted into a parso from-import node. prefix (str, optional): The leading whitespace (indentation) placed before the new import. Returns: :class:`parso.python.tree.ImportFrom`: A newly generated import statement. """ base_names = namespace_parts[:-1] base_count = len(base_names) base_nodes = [] if base_count > 1: for is_last, name in iterbot.iter_is_last(base_names): base_nodes.append(tree.Name(name, (0, 0))) if not is_last: base_nodes.append(tree.Operator(".", (0, 0))) base_nodes[0].prefix = " " base = tree.PythonNode("dotted_name", base_nodes) else: base = tree.Name(base_names[0], (0, 0), prefix=" ") tail = tree.Name(namespace_parts[-1], (0, 0), prefix=" ") if alias: tail = tree.PythonNode( "import_as_name", [tail, tree.Keyword("as", (0, 0), prefix=" "), alias]) return tree.ImportFrom([ tree.Keyword("from", (0, 0), prefix=prefix), base, tree.Keyword("import", (0, 0), prefix=" "), tail, ])
def modify_with_existing( # pylint: disable=arguments-differ cls, graph, data, append=False): """Add `data` to a parso node `graph`. Reference: https://github.com/nerdvegas/rez/wiki/Package-Definition-Guide#help Args: graph (:class:`parso.python.Tree.PythonBaseNode`): Some node that may assignment an attribute called "tests". If "tests" assignment exists, it gets overwritten. If no assignment exists then a new assignment is appended to the end of the file. data (list[list[str]] or str or :class:`parso.python.tree.PythonNode`): Any values that'd typically define a Rez "tests" attribute. Basically anything is allowed, as long the Rez package schema considers it valid. append (bool, optional): If False, anything in `data` will override the objects in `graph` if there are any conflicts between the two. If True, the conflicts are ignored and `data` is just added to `graph` is if no conflict exists. Default is False. Returns: str: The modified Python source code. It should resemble the source code of `graph` plus any serialized `data`. """ assignments = parso_utility.find_assignment_nodes( "help", graph) or parso_utility.find_definition_root_nodes( "help", graph) try: assignment = assignments[-1] except IndexError: assignment = None if isinstance(data, tree.BaseNode): graph = convention.insert_or_append_raw_node( data, graph, assignment, "help") return graph.get_code() help_data = parso.parse(json.dumps( data, cls=encoder.BuiltinEncoder)).children[0] if (assignment and not _get_list_root(assignment) and isinstance(help_data, tree.String)): help_data.prefix = " " graph = convention.insert_or_append(help_data, graph, assignment, "help") return graph.get_code() if _get_list_root(assignment) and isinstance(help_data, tree.String): raise ValueError( 'You may not add string "{help_data}" ' 'because assignment "{assignment}" is a list.'.format( help_data=help_data.get_code(), assignment=assignment.get_code())) help_data = _get_inner_list_entries(help_data) entries = [] if assignment: entries = cls._get_entries(assignment) if not append: entries = cls._resolve_entries(entries, help_data) else: entries.extend(help_data) entries = [ tree.PythonNode("atom", [ tree.Operator("[", (0, 0)), entry, tree.Operator("]", (0, 0)) ]) for entry in entries ] entries = _separate_with_commas(entries) node = tree.PythonNode( "atom", [tree.Operator("[", (0, 0))] + entries + [tree.Operator(",", (0, 0)), tree.Operator("]", (0, 0))], ) node = _apply_formatting(node) graph = convention.insert_or_append(node, graph, assignment, "help") return graph.get_code()