docs: def tree2text(tree_obj, indent=4):
      # type: (TreeInfo, int) -> str
      """
      Return text representation of a decision tree.
      """
      parts = []
  
      def _format_node(node, depth=0):
          # type: (NodeInfo, int) -> None
          def p(*args):
              # type: (*str) -> None
              parts.append(" " * depth * indent)
              parts.extend(args)
  
          if node.is_leaf:
              value_repr = _format_leaf_value(tree_obj, node)
              parts.append("  ---> {}".format(value_repr))
          else:
              assert node.left is not None
              assert node.right is not None
              feat_name = node.feature_name
  
              if depth > 0:
                  parts.append("\n")
              left_samples = node.left.sample_ratio
              p("{feat_name} <= {threshold:0.3f}  ({left_samples:0.1%})".format(
                  left_samples=left_samples,
                  feat_name=feat_name,
                  threshold=node.threshold,
              ))
              _format_node(node.left, depth=depth + 1)
  
              parts.append("\n")
              right_samples = node.right.sample_ratio
              p("{feat_name} > {threshold:0.3f}  ({right_samples:0.1%})".format(
                  right_samples=right_samples,
                  feat_name=feat_name,
                  threshold=node.threshold,
                  ))
              _format_node(node.right, depth=depth + 1)
  
      _format_node(tree_obj.tree)
      return "".join(parts)