Exemple #1
0
 def test_get_names(self):
     assert YAMLValueFormats.get_names() == [
         "BARE",
         "BOOLEAN",
         "DEFAULT",
         "DQUOTE",
         "FLOAT",
         "FOLDED",
         "INT",
         "LITERAL",
         "SQUOTE",
     ]
    def test_preserve_old_blockiness(self, quiet_logger, eyamldata_f, old_eyaml_keys, yaml_path, newval, eoformat, yvformat):
        processor = EYAMLProcessor(quiet_logger, eyamldata_f, privatekey=old_eyaml_keys[0], publickey=old_eyaml_keys[1])
        processor.set_eyaml_value(yaml_path, newval, output=eoformat)

        encvalue = None
        encformat = YAMLValueFormats.DEFAULT
        for encnode in processor.get_nodes(yaml_path):
            encvalue = encnode
            encformat = YAMLValueFormats.from_node(encvalue)
            break

        assert EYAMLProcessor.is_eyaml_value(encvalue) and yvformat == encformat
Exemple #3
0
def main():
    """Main code."""
    args = processcli()
    log = ConsolePrinter(args)
    validateargs(args, log)
    change_path = YAMLPath(args.change, pathsep=args.pathsep)
    must_exist = args.mustexist or args.saveto

    # Obtain the replacement value
    consumed_stdin = False
    new_value = None
    has_new_value = False
    if args.value or args.value == "":
        new_value = args.value
        has_new_value = True
    elif args.stdin:
        new_value = ''.join(sys.stdin.readlines())
        consumed_stdin = True
        has_new_value = True
    elif args.file:
        with open(args.file, 'r') as fhnd:
            new_value = fhnd.read().rstrip()
        has_new_value = True
    elif args.null:
        new_value = None
        has_new_value = True
    elif args.random is not None:
        new_value = ''.join(
            secrets.choice(args.random_from) for _ in range(args.random))
        has_new_value = True

    # Prep the YAML parser
    yaml = Parsers.get_yaml_editor()

    # Attempt to open the YAML file; check for parsing errors
    if args.yaml_file:
        yaml_data = _try_load_input_file(args, log, yaml, change_path,
                                         new_value)
        if args.yaml_file.strip() == '-':
            consumed_stdin = True

    # Check for a waiting STDIN document
    if (not consumed_stdin and not args.yaml_file and not args.nostdin
            and not sys.stdin.isatty()):
        args.yaml_file = "-"
        yaml_data = _try_load_input_file(args, log, yaml, change_path,
                                         new_value)

    # Load the present nodes at the specified YAML Path
    processor = EYAMLProcessor(log,
                               yaml_data,
                               binary=args.eyaml,
                               publickey=args.publickey,
                               privatekey=args.privatekey)
    change_node_coordinates = _get_nodes(
        log,
        processor,
        change_path,
        must_exist=must_exist,
        default_value=("" if new_value else " "))

    old_format = YAMLValueFormats.DEFAULT
    if len(change_node_coordinates) == 1:
        # When there is exactly one result, its old format can be known.  This
        # is necessary to retain whether the replacement value should be
        # represented later as a multi-line string when the new value is to be
        # encrypted.
        old_format = YAMLValueFormats.from_node(
            change_node_coordinates[0].node)

    # Check the value(s), if desired
    if args.check:
        for node_coordinate in change_node_coordinates:
            if processor.is_eyaml_value(node_coordinate.node):
                # Sanity check:  If either --publickey or --privatekey were set
                # then they must both be set in order to decrypt this value.
                # This is enforced only when the value must be decrypted due to
                # a --check request.
                if ((args.publickey and not args.privatekey)
                        or (args.privatekey and not args.publickey)):
                    log.error(
                        "Neither or both private and public EYAML keys must be"
                        + " set when --check is required to decrypt the old" +
                        " value.")
                    sys.exit(1)

                try:
                    check_value = processor.decrypt_eyaml(node_coordinate.node)
                except EYAMLCommandException as ex:
                    log.critical(ex, 1)
            else:
                check_value = node_coordinate.node

            if not args.check == check_value:
                log.critical(
                    '"{}" does not match the check value.'.format(args.check),
                    20)

    # Save the old value, if desired and possible
    if args.saveto:
        # Only one can be saved; otherwise it is impossible to meaningfully
        # convey to the end-user from exactly which other YAML node each saved
        # value came.
        if len(change_node_coordinates) > 1:
            log.critical(
                "It is impossible to meaningly save more than one matched" +
                " value.  Please omit --saveto or set --change to affect" +
                " exactly one value.", 1)

        saveto_path = YAMLPath(args.saveto, pathsep=args.pathsep)
        log.verbose("Saving the old value to {}.".format(saveto_path))

        # Folded EYAML values have their embedded newlines converted to spaces
        # when read.  As such, writing them back out breaks their original
        # format, despite being properly typed.  To restore the original
        # written form, reverse the conversion, here.
        old_value = change_node_coordinates[0].node
        if ((old_format is YAMLValueFormats.FOLDED
             or old_format is YAMLValueFormats.LITERAL)
                and EYAMLProcessor.is_eyaml_value(old_value)):
            old_value = old_value.replace(" ", "\n")

        try:
            processor.set_value(saveto_path,
                                Nodes.clone_node(old_value),
                                value_format=old_format,
                                tag=args.tag)
        except YAMLPathException as ex:
            log.critical(ex, 1)

    # Set the requested value
    log.verbose("Applying changes to {}.".format(change_path))
    if args.delete:
        # Destroy the collected nodes (from their parents) in the reverse order
        # they were discovered.  This is necessary lest Array elements be
        # improperly handled, leading to unwanted data loss.
        _delete_nodes(log, processor, change_node_coordinates)
    elif args.aliasof:
        # Assign the change nodes as Aliases of whatever --aliasof points to
        _alias_nodes(log, processor, change_node_coordinates, args.aliasof,
                     args.anchor)
    elif args.eyamlcrypt:
        # If the user hasn't specified a format, use the same format as the
        # value being replaced, if known.
        format_type = YAMLValueFormats.from_str(args.format)
        if format_type is YAMLValueFormats.DEFAULT:
            format_type = old_format

        output_type = EYAMLOutputFormats.STRING
        if format_type in [YAMLValueFormats.FOLDED, YAMLValueFormats.LITERAL]:
            output_type = EYAMLOutputFormats.BLOCK

        try:
            processor.set_eyaml_value(change_path,
                                      new_value,
                                      output=output_type,
                                      mustexist=False)
        except EYAMLCommandException as ex:
            log.critical(ex, 2)
    elif has_new_value:
        try:
            processor.set_value(change_path,
                                new_value,
                                value_format=args.format,
                                mustexist=must_exist,
                                tag=args.tag)
        except YAMLPathException as ex:
            log.critical(ex, 1)
    elif args.tag:
        _tag_nodes(processor.data, args.tag, change_node_coordinates)

    # Write out the result
    write_output_document(args, log, yaml, yaml_data)
Exemple #4
0
def processcli():
    """Process command-line arguments."""
    parser = argparse.ArgumentParser(
        description=(
            "Changes one or more Scalar values in a YAML/JSON/Compatible"
            " document at a specified YAML Path.  Matched values can be"
            " checked before they are replaced to mitigate accidental change."
            "  When matching singular results, the value can be archived to"
            " another key before it is replaced.  Further, EYAML can be"
            " employed to encrypt the new values and/or decrypt an old value"
            " before checking it."),
        epilog=(
            "When no changes are made, no backup is created, even when"
            " -b/--backup is specified.  For more information about YAML"
            " Paths, please visit https://github.com/wwkimball/yamlpath/wiki."
            "  To report issues with this tool or to request enhancements,"
            " please visit https://github.com/wwkimball/yamlpath/issues."))
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version="%(prog)s " + YAMLPATH_VERSION)

    required_group = parser.add_argument_group("required settings")
    required_group.add_argument(
        "-g",
        "--change",
        required=True,
        metavar="YAML_PATH",
        help="YAML Path where the target value is found")

    inputex_group = parser.add_argument_group("input options")
    input_group = inputex_group.add_mutually_exclusive_group()
    input_group.add_argument(
        "-a",
        "--value",
        help="set the new value from the command-line instead of STDIN")
    input_group.add_argument(
        "-A",
        "--aliasof",
        metavar="ANCHOR",
        help="set the value as a YAML Alias of an existing Anchor, by name "
        "(merely copies the target value for non-YAML files)")
    input_group.add_argument(
        "-f",
        "--file",
        help="read the new value from file (discarding any trailing\
              new-lines)")
    input_group.add_argument(
        "-i",
        "--stdin",
        action="store_true",
        help="accept the new value from STDIN (best for sensitive data)")
    input_group.add_argument(
        "-R",
        "--random",
        type=int,
        metavar="LENGTH",
        help="randomly generate a replacement value of a set length")
    input_group.add_argument("-N",
                             "--null",
                             action="store_true",
                             help="sets the value to null")
    input_group.add_argument(
        "-D",
        "--delete",
        action="store_true",
        help="delete rather than change target node(s); implies"
        " --mustexist|-m")

    parser.add_argument(
        "-F",
        "--format",
        default="default",
        choices=[l.lower() for l in YAMLValueFormats.get_names()],
        type=str.lower,
        help="override automatic formatting of the new value")
    parser.add_argument("-c",
                        "--check",
                        help="check the value before replacing it")
    parser.add_argument(
        "-s",
        "--saveto",
        metavar="YAML_PATH",
        help="save the old value to YAML_PATH before replacing it; implies\
              --mustexist")
    parser.add_argument(
        "-m",
        "--mustexist",
        action="store_true",
        help="require that the --change YAML_PATH already exist in YAML_FILE")
    parser.add_argument(
        "-b",
        "--backup",
        action="store_true",
        help="save a backup YAML_FILE with an extra .bak file-extension")
    parser.add_argument(
        "-t",
        "--pathsep",
        default="dot",
        choices=PathSeperators,
        metavar=PathSeperators.get_choices(),
        type=PathSeperators.from_str,
        help="indicate which YAML Path seperator to use when rendering\
              results; default=dot")

    parser.add_argument(
        "-M",
        "--random-from",
        metavar="CHARS",
        default=(string.ascii_uppercase + string.ascii_lowercase +
                 string.digits),
        help="characters from which to build a value for --random; default="
        "all upper- and lower-case letters and all digits")
    parser.add_argument(
        "-H",
        "--anchor",
        metavar="ANCHOR",
        help="when --aliasof|-A points to a value which is not already"
        " Anchored, a new Anchor with this name is created; renames an"
        " existing Anchor if already set")
    parser.add_argument(
        "-T",
        "--tag",
        metavar="TAG",
        help="assign a custom YAML (data-type) tag to the changed nodes; can"
        " be used without other input options to assign or change a tag")

    eyaml_group = parser.add_argument_group(
        "EYAML options", "Left unset, the EYAML keys will default to your\
         system or user defaults.  You do not need to supply a private key\
         unless you enable --check and the old value is encrypted.")
    eyaml_group.add_argument("-e",
                             "--eyamlcrypt",
                             action="store_true",
                             help="encrypt the new value using EYAML")
    eyaml_group.add_argument(
        "-x",
        "--eyaml",
        default="eyaml",
        help="the eyaml binary to use when it isn't on the PATH")
    eyaml_group.add_argument("-r", "--privatekey", help="EYAML private key")
    eyaml_group.add_argument("-u", "--publickey", help="EYAML public key")

    parser.add_argument(
        "-S",
        "--nostdin",
        action="store_true",
        help=("Do not implicitly read from STDIN, even when there is no"
              " YAML_FILE with a non-TTY session"))

    noise_group = parser.add_mutually_exclusive_group()
    noise_group.add_argument("-d",
                             "--debug",
                             action="store_true",
                             help="output debugging details")
    noise_group.add_argument("-v",
                             "--verbose",
                             action="store_true",
                             help="increase output verbosity")
    noise_group.add_argument("-q",
                             "--quiet",
                             action="store_true",
                             help="suppress all output except errors")

    parser.add_argument(
        "yaml_file",
        metavar="YAML_FILE",
        nargs="?",
        help="the YAML file to update; omit or use - to read from STDIN")
    return parser.parse_args()
Exemple #5
0
 def test_from_node(self, input, output):
     assert output == YAMLValueFormats.from_node(input)
Exemple #6
0
 def test_from_str_nameerror(self):
     with pytest.raises(NameError):
         YAMLValueFormats.from_str("NO SUCH NAME")
Exemple #7
0
def processcli():
    """Process command-line arguments."""
    parser = argparse.ArgumentParser(
        description="Changes one or more values in a YAML file at a specified\
            YAML Path.  Matched values can be checked before they are replaced\
            to mitigate accidental change. When matching singular results, the\
            value can be archived to another key before it is replaced.\
            Further, EYAML can be employed to encrypt the new values and/or\
            decrypt an old value before checking them.",
        epilog="When no changes are made, no backup is created, even when\
            -b/--backup is specified.  For more information about YAML Paths,\
            please visit https://github.com/wwkimball/yamlpath.")
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version="%(prog)s " + MY_VERSION)

    required_group = parser.add_argument_group("required settings")
    required_group.add_argument(
        "-g",
        "--change",
        required=True,
        metavar="YAML_PATH",
        help="YAML Path where the target value is found")

    inputex_group = parser.add_argument_group("input options")
    input_group = inputex_group.add_mutually_exclusive_group()
    input_group.add_argument(
        "-a",
        "--value",
        help="set the new value from the command-line instead of STDIN")
    input_group.add_argument(
        "-f",
        "--file",
        help="read the new value from file (discarding any trailing\
              new-lines)")
    input_group.add_argument(
        "-i",
        "--stdin",
        action="store_true",
        help="accept the new value from STDIN (best for sensitive data)")
    input_group.add_argument(
        "-R",
        "--random",
        type=int,
        metavar="LENGTH",
        help="randomly generate a replacement value of a set length")

    parser.add_argument(
        "-F",
        "--format",
        default="default",
        choices=[l.lower() for l in YAMLValueFormats.get_names()],
        type=str.lower,
        help="override automatic formatting of the new value")
    parser.add_argument("-c",
                        "--check",
                        help="check the value before replacing it")
    parser.add_argument(
        "-s",
        "--saveto",
        metavar="YAML_PATH",
        help="save the old value to YAML_PATH before replacing it; implies\
              --mustexist")
    parser.add_argument(
        "-m",
        "--mustexist",
        action="store_true",
        help="require that the --change YAML_PATH already exist in YAML_FILE")
    parser.add_argument(
        "-b",
        "--backup",
        action="store_true",
        help="save a backup YAML_FILE with an extra .bak file-extension")
    parser.add_argument(
        "-t",
        "--pathsep",
        default="dot",
        choices=PathSeperators,
        metavar=PathSeperators.get_choices(),
        type=PathSeperators.from_str,
        help="indicate which YAML Path seperator to use when rendering\
              results; default=dot")

    eyaml_group = parser.add_argument_group(
        "EYAML options", "Left unset, the EYAML keys will default to your\
         system or user defaults.  You do not need to supply a private key\
         unless you enable --check and the old value is encrypted.")
    eyaml_group.add_argument("-e",
                             "--eyamlcrypt",
                             action="store_true",
                             help="encrypt the new value using EYAML")
    eyaml_group.add_argument(
        "-x",
        "--eyaml",
        default="eyaml",
        help="the eyaml binary to use when it isn't on the PATH")
    eyaml_group.add_argument("-r", "--privatekey", help="EYAML private key")
    eyaml_group.add_argument("-u", "--publickey", help="EYAML public key")

    noise_group = parser.add_mutually_exclusive_group()
    noise_group.add_argument("-d",
                             "--debug",
                             action="store_true",
                             help="output debugging details")
    noise_group.add_argument("-v",
                             "--verbose",
                             action="store_true",
                             help="increase output verbosity")
    noise_group.add_argument("-q",
                             "--quiet",
                             action="store_true",
                             help="suppress all output except errors")

    parser.add_argument("yaml_file",
                        metavar="YAML_FILE",
                        help="the YAML file to update")
    return parser.parse_args()
Exemple #8
0
def main():
    """Main code."""
    args = processcli()
    log = ConsolePrinter(args)
    validateargs(args, log)
    change_path = YAMLPath(args.change, pathsep=args.pathsep)
    backup_file = args.yaml_file + ".bak"

    # Obtain the replacement value
    if args.value:
        new_value = args.value
    elif args.stdin:
        new_value = ''.join(sys.stdin.readlines())
    elif args.file:
        with open(args.file, 'r') as fhnd:
            new_value = fhnd.read().rstrip()
    elif args.random is not None:
        new_value = ''.join(
            secrets.choice(string.ascii_uppercase + string.ascii_lowercase +
                           string.digits) for _ in range(args.random))

    # Prep the YAML parser
    yaml = get_yaml_editor()

    # Attempt to open the YAML file; check for parsing errors
    yaml_data = get_yaml_data(yaml, log, args.yaml_file)
    if yaml_data is None:
        # An error message has already been logged
        exit(1)

    # Load the present value at the specified YAML Path
    change_nodes = []
    old_format = YAMLValueFormats.DEFAULT
    processor = EYAMLProcessor(log,
                               yaml_data,
                               binary=args.eyaml,
                               publickey=args.publickey,
                               privatekey=args.privatekey)
    try:
        for node in processor.get_nodes(
                change_path,
                mustexist=(args.mustexist or args.saveto),
                default_value=("" if new_value else " ")):
            log.debug('Got "{}" from {}.'.format(node, change_path))
            change_nodes.append(node)
    except YAMLPathException as ex:
        log.critical(ex, 1)

    if len(change_nodes) == 1:
        # When there is exactly one result, its old format can be known.  This
        # is necessary to retain whether the replacement value should be
        # represented later as a multi-line string when the new value is to be
        # encrypted.
        old_format = YAMLValueFormats.from_node(change_nodes[0])

    log.debug("Collected nodes:")
    log.debug(change_nodes)

    # Check the value(s), if desired
    if args.check:
        for node in change_nodes:
            if processor.is_eyaml_value(node):
                # Sanity check:  If either --publickey or --privatekey were set
                # then they must both be set in order to decrypt this value.
                # This is enforced only when the value must be decrypted due to
                # a --check request.
                if ((args.publickey and not args.privatekey)
                        or (args.privatekey and not args.publickey)):
                    log.error(
                        "Neither or both private and public EYAML keys must be"
                        + " set when --check is required to decrypt the old" +
                        " value.")
                    exit(1)

                try:
                    check_value = processor.decrypt_eyaml(node)
                except EYAMLCommandException as ex:
                    log.critical(ex, 1)
            else:
                check_value = node

            if not args.check == check_value:
                log.critical(
                    '"{}" does not match the check value.'.format(args.check),
                    20)

    # Save the old value, if desired and possible
    if args.saveto:
        # Only one can be saved; otherwise it is impossible to meaningfully
        # convey to the end-user from exactly which other YAML node each saved
        # value came.
        if len(change_nodes) > 1:
            log.critical(
                "It is impossible to meaningly save more than one matched" +
                " value.  Please omit --saveto or set --change to affect" +
                " exactly one value.", 1)

        saveto_path = YAMLPath(args.saveto, pathsep=args.pathsep)
        log.verbose("Saving the old value to {}.".format(saveto_path))

        # Folded EYAML values have their embedded newlines converted to spaces
        # when read.  As such, writing them back out breaks their original
        # format, despite being properly typed.  To restore the original
        # written form, reverse the conversion, here.
        old_value = change_nodes[0]
        if ((old_format is YAMLValueFormats.FOLDED
             or old_format is YAMLValueFormats.LITERAL)
                and EYAMLProcessor.is_eyaml_value(old_value)):
            old_value = old_value.replace(" ", "\n")

        try:
            processor.set_value(saveto_path,
                                clone_node(old_value),
                                value_format=old_format)
        except YAMLPathException as ex:
            log.critical(ex, 1)

    # Set the requested value
    log.verbose("Setting the new value for {}.".format(change_path))
    if args.eyamlcrypt:
        # If the user hasn't specified a format, use the same format as the
        # value being replaced, if known.
        format_type = YAMLValueFormats.from_str(args.format)
        if format_type is YAMLValueFormats.DEFAULT:
            format_type = old_format

        output_type = EYAMLOutputFormats.STRING
        if format_type in [YAMLValueFormats.FOLDED, YAMLValueFormats.LITERAL]:
            output_type = EYAMLOutputFormats.BLOCK

        try:
            processor.set_eyaml_value(change_path,
                                      new_value,
                                      output=output_type,
                                      mustexist=False)
        except EYAMLCommandException as ex:
            log.critical(ex, 2)
    else:
        processor.set_value(change_path, new_value, value_format=args.format)

    # Save a backup of the original file, if requested
    if args.backup:
        log.verbose("Saving a backup of {} to {}.".format(
            args.yaml_file, backup_file))
        if exists(backup_file):
            remove(backup_file)
        copy2(args.yaml_file, backup_file)

    # Save the changed file
    log.verbose("Writing changed data to {}.".format(args.yaml_file))
    with open(args.yaml_file, 'w') as yaml_dump:
        yaml.dump(yaml_data, yaml_dump)
Exemple #9
0
    def make_new_node(source_node: Any, value: Any,
                      value_format: YAMLValueFormats, **kwargs) -> Any:
        """
        Create a new data node based on a sample node.

        This is achieved by effectively duplicaing the type and anchor of the
        node but giving it a different value.

        Parameters:
        1. source_node (Any) The node from which to copy type
        2. value (Any) The value to assign to the new node
        3. value_format (YAMLValueFormats) The YAML presentation format to
           apply to value when it is dumped

        Keyword Arguments:
        * tag (str) Custom data-type tag to apply to this node

        Returns: (Any) The new node

        Raises:
        - `NameError` when value_format is invalid
        - `ValueError' when the new value is not numeric and value_format
        requires it to be so
        """
        new_node = None
        new_type = type(source_node)
        new_value = value
        valform = YAMLValueFormats.DEFAULT

        if isinstance(value_format, YAMLValueFormats):
            valform = value_format
        else:
            strform = str(value_format)
            try:
                valform = YAMLValueFormats.from_str(strform)
            except NameError as wrap_ex:
                raise NameError(
                    "Unknown YAML Value Format:  {}".format(strform) +
                    ".  Please specify one of:  " + ", ".join(
                        [l.lower()
                         for l in YAMLValueFormats.get_names()])) from wrap_ex

        if valform == YAMLValueFormats.BARE:
            new_type = PlainScalarString
            new_value = str(value)
        elif valform == YAMLValueFormats.DQUOTE:
            new_type = DoubleQuotedScalarString
            new_value = str(value)
        elif valform == YAMLValueFormats.SQUOTE:
            new_type = SingleQuotedScalarString
            new_value = str(value)
        elif valform == YAMLValueFormats.FOLDED:
            new_type = FoldedScalarString
            new_value = str(value)
            preserve_folds = []

            # Scan all except the very last character because if that last
            # character is a newline, ruamel.yaml will crash when told to fold
            # there.
            for index, fold_char in enumerate(new_value[:-1]):
                if fold_char == "\n":
                    preserve_folds.append(index)

            # Replace all except the very last character
            new_value = new_value[:-1].replace("\n", " ") + new_value[-1]

            if hasattr(source_node, "anchor") and source_node.anchor.value:
                new_node = new_type(new_value, anchor=source_node.anchor.value)
            else:
                new_node = new_type(new_value)

            if preserve_folds:
                new_node.fold_pos = preserve_folds  # type: ignore

        elif valform == YAMLValueFormats.LITERAL:
            new_type = LiteralScalarString
            new_value = str(value)
        elif valform == YAMLValueFormats.BOOLEAN:
            new_type = ScalarBoolean
            if isinstance(value, bool):
                new_value = value
            else:
                new_value = strtobool(value)
        elif valform == YAMLValueFormats.FLOAT:
            try:
                new_value = float(value)
            except ValueError as wrap_ex:
                raise ValueError(
                    ("The requested value format is {}, but '{}' cannot be" +
                     " cast to a floating-point number.").format(
                         valform, value)) from wrap_ex

            anchor_val = None
            if hasattr(source_node, "anchor"):
                anchor_val = source_node.anchor.value
            new_node = Nodes.make_float_node(new_value, anchor_val)
        elif valform == YAMLValueFormats.INT:
            new_type = ScalarInt

            try:
                new_value = int(value)
            except ValueError as wrap_ex:
                raise ValueError(
                    ("The requested value format is {}, but '{}' cannot be" +
                     " cast to an integer number.").format(valform,
                                                           value)) from wrap_ex
        else:
            # Punt to whatever the best Scalar type may be
            try:
                wrapped_value = Nodes.wrap_type(value)
            except ValueError:
                # Value cannot be safely converted to any native type
                new_type = PlainScalarString
                wrapped_value = PlainScalarString(value)

            if Nodes.node_is_leaf(wrapped_value):
                new_type = type(wrapped_value)
            else:
                # Disallow conversions to complex types
                new_type = PlainScalarString
                wrapped_value = PlainScalarString(value)

            new_format = YAMLValueFormats.from_node(wrapped_value)
            if new_format is not YAMLValueFormats.DEFAULT:
                new_node = Nodes.make_new_node(source_node, value, new_format,
                                               **kwargs)

        if new_node is None:
            if hasattr(source_node, "anchor") and source_node.anchor.value:
                new_node = new_type(new_value, anchor=source_node.anchor.value)
            elif new_type is not type(None):
                new_node = new_type(new_value)

        # Apply a custom tag, if provided
        if "tag" in kwargs:
            new_node = Nodes.apply_yaml_tag(new_node, kwargs.pop("tag"))

        return new_node
Exemple #10
0
def make_new_node(source_node: Any, value: Any,
                  value_format: YAMLValueFormats) -> Any:
    """
    Creates a new data node based on a sample node, effectively duplicaing
    the type and anchor of the node but giving it a different value.

    Parameters:
        1. source_node (Any) The node from which to copy type
        2. value (Any) The value to assign to the new node
        3. value_format (YAMLValueFormats) The YAML presentation format to
            apply to value when it is dumped

    Returns: (Any) The new node

    Raises:
        - `NameError` when value_format is invalid
        - `ValueError' when the new value is not numeric and value_format
            requires it to be so
    """
    new_node = None
    new_type = type(source_node)
    new_value = value
    valform = YAMLValueFormats.DEFAULT

    if isinstance(value_format, YAMLValueFormats):
        valform = value_format
    else:
        strform = str(value_format)
        try:
            valform = YAMLValueFormats.from_str(strform)
        except NameError:
            raise NameError(
                "Unknown YAML Value Format:  {}".format(strform) +
                ".  Please specify one of:  " +
                ", ".join([l.lower() for l in YAMLValueFormats.get_names()]))

    if valform == YAMLValueFormats.BARE:
        new_type = PlainScalarString
        new_value = str(value)
    elif valform == YAMLValueFormats.DQUOTE:
        new_type = DoubleQuotedScalarString
        new_value = str(value)
    elif valform == YAMLValueFormats.SQUOTE:
        new_type = SingleQuotedScalarString
        new_value = str(value)
    elif valform == YAMLValueFormats.FOLDED:
        new_type = FoldedScalarString
        new_value = str(value)
    elif valform == YAMLValueFormats.LITERAL:
        new_type = LiteralScalarString
        new_value = str(value)
    elif valform == YAMLValueFormats.BOOLEAN:
        new_type = ScalarBoolean
        if isinstance(value, bool):
            new_value = value
        else:
            new_value = strtobool(value)
    elif valform == YAMLValueFormats.FLOAT:
        try:
            new_value = float(value)
        except ValueError:
            raise ValueError(
                ("The requested value format is {}, but '{}' cannot be" +
                 " cast to a floating-point number.").format(valform, value))

        strval = str(value)
        precision = 0
        width = len(strval)
        lastdot = strval.rfind(".")
        if -1 < lastdot:
            precision = strval.rfind(".")

        if hasattr(source_node, "anchor"):
            new_node = ScalarFloat(new_value,
                                   anchor=source_node.anchor.value,
                                   prec=precision,
                                   width=width)
        else:
            new_node = ScalarFloat(new_value, prec=precision, width=width)
    elif valform == YAMLValueFormats.INT:
        new_type = ScalarInt

        try:
            new_value = int(value)
        except ValueError:
            raise ValueError(
                ("The requested value format is {}, but '{}' cannot be" +
                 " cast to an integer number.").format(valform, value))
    else:
        # Punt to whatever the best type may be
        new_type = type(wrap_type(value))

    if new_node is None:
        if hasattr(source_node, "anchor"):
            new_node = new_type(new_value, anchor=source_node.anchor.value)
        else:
            new_node = new_type(new_value)

    return new_node