All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.virtuslab.yaml.NodeVisitor.scala Maven / Gradle / Ivy

There is a newer version: 0.3.0
Show newest version
package org.virtuslab.yaml

import org.virtuslab.yaml.Node
import org.virtuslab.yaml.Node._
import org.virtuslab.yaml.Range
import org.virtuslab.yaml.ModifyError
import org.virtuslab.yaml.YamlError
import org.virtuslab.yaml.syntax.NodeSelector
import org.virtuslab.yaml.syntax.NodeSelector._

class NodeVisitor(node: Node, selectors: List[NodeSelector]) {
  def apply(index: Int): NodeVisitor =
    NodeVisitor(node, selectors :+ NodeSelector.IntSelector(index))
  def apply(field: String): NodeVisitor =
    NodeVisitor(node, selectors :+ NodeSelector.StringSelector(field))

  private def updateScalarNode(
      modifyValue: String => String,
      scalar: ScalarNode
  ): Either[ModifyError, ScalarNode] =
    selectors match {
      case Nil =>
        Right(
          scalar.copy(
            value = modifyValue(scalar.value)
          )
        )
      case _ =>
        Left(
          ModifyError(
            s"Expected end of scalar path, instead found path ${selectors.map(_.show).mkString(".")}"
          )
        )
    }

  private def removeScalarNode(scalar: ScalarNode): Either[ModifyError, ScalarNode] =
    selectors match {
      case Nil =>
        Right(
          scalar.copy(
            value = "",
            tag = Tag.nullTag
          )
        )
      case _ =>
        Left(
          ModifyError(
            s"Expected end of scalar path, instead found path ${selectors.map(_.show).mkString(".")}"
          )
        )
    }

  private def updateSequenceNode(
      modifyValue: String => String,
      sequence: SequenceNode
  ): Either[ModifyError, SequenceNode] =
    selectors match {
      case IntSelector(index) :: rest =>
        val nodes      = sequence.nodes
        val updateNode = NodeVisitor(nodes(index), rest).modifyValue(modifyValue)
        updateNode.map(node => sequence.copy(nodes = nodes.updated(index, node)))

      case StringSelector(field) :: rest =>
        Left(
          ModifyError(
            s"Found string path: ${field} traversing sequence, but index was expected"
          )
        )
      case _ => Left(ModifyError(s"Found end of path, but index was expected"))
    }

  private def removeSequenceNode(
      sequence: SequenceNode
  ): Either[ModifyError, SequenceNode] =
    selectors match {
      case IntSelector(index) :: rest =>
        if (rest.isEmpty) {
          Right(
            sequence.copy(
              nodes = sequence.nodes.patch(index, Nil, 1)
            )
          )
        } else {
          val nodes      = sequence.nodes
          val updateNode = NodeVisitor(nodes(index), rest).removeValue()
          updateNode.map(node => sequence.copy(nodes = nodes.updated(index, node)))
        }
      case StringSelector(field) :: rest =>
        Left(
          ModifyError(
            s"Expected index of sequence, instead found string path: ${field}"
          )
        )
      case _ => Left(ModifyError(s"Expected index of sequence, instead found end of path"))
    }

  private def updateMappingNode(modifyValue: String => String, mapping: MappingNode) =
    selectors match {
      case StringSelector(field) :: rest =>
        val mappings = mapping.mappings
        val entryToUpdateOpt = mappings.find {
          case (ScalarNode(keyName, _), _) => keyName == field
          case _                           => false
        }

        entryToUpdateOpt match {
          case Some(entryToUpdate) =>
            val updatedValueE = entryToUpdate match {
              case (ScalarNode(keyName, _), valueNode) =>
                val updatedNode = NodeVisitor(valueNode, rest).modifyValue(modifyValue)
                updatedNode
              case _ => Left(ModifyError(s"Not found $field in mapping"))
            }

            updatedValueE.map { updatedValue =>
              mapping.copy(
                mappings.updated(entryToUpdate._1, updatedValue)
              )
            }
          case None => Left(ModifyError(s"Not found $field in mapping"))
        }
      case IntSelector(index) :: rest =>
        Left(
          ModifyError(
            s"Expected plain test, instead found index: $index"
          )
        )
      case _ => Left(ModifyError(s"Expected plain text, instead found end of path"))
    }

  private def removeMappingNode(mapping: MappingNode) =
    selectors match {
      case StringSelector(field) :: rest =>
        if (rest.isEmpty) {
          Right(
            mapping.copy(
              mappings = mapping.mappings.filter {
                case (ScalarNode(keyName, _), _) => keyName != field
                case _                           => false
              }
            )
          )
        } else {
          val mappings = mapping.mappings
          val entryToUpdateOpt = mappings.find {
            case (ScalarNode(keyName, _), _) => keyName == field
            case _                           => false
          }

          entryToUpdateOpt match {
            case Some(entryToUpdate) =>
              val updatedValueE = entryToUpdate match {
                case (ScalarNode(keyName, _), valueNode) =>
                  val updatedNode = NodeVisitor(valueNode, rest).removeValue()
                  updatedNode
                case _ => Left(ModifyError(s"Not found $field in mapping"))
              }

              updatedValueE.map { updatedValue =>
                mapping.copy(
                  mappings.updated(entryToUpdate._1, updatedValue)
                )
              }
            case None => Left(ModifyError(s"Not found $field in mapping"))
          }
        }
      case IntSelector(index) :: rest =>
        Left(
          ModifyError(
            s"Expected plain test, instead found index: $index"
          )
        )
      case _ => Left(ModifyError(s"Expected plain text, instead found end of path"))
    }

  def modifyValue(fn: String => String): Either[ModifyError, Node] = node match {
    case scalar: ScalarNode     => updateScalarNode(fn, scalar)
    case sequence: SequenceNode => updateSequenceNode(fn, sequence)
    case mapping: MappingNode   => updateMappingNode(fn, mapping)
  }

  def setValue(value: String): Either[ModifyError, Node] = node match {
    case scalar: ScalarNode     => updateScalarNode((_) => value, scalar)
    case sequence: SequenceNode => updateSequenceNode((_) => value, sequence)
    case mapping: MappingNode   => updateMappingNode((_) => value, mapping)
  }

  def removeValue(): Either[ModifyError, Node] = node match {
    case scalar: ScalarNode     => removeScalarNode(scalar)
    case sequence: SequenceNode => removeSequenceNode(sequence)
    case mapping: MappingNode   => removeMappingNode(mapping)
  }
}

object NodeVisitor {

  def apply(node: Node, selectors: List[NodeSelector]): NodeVisitor =
    new NodeVisitor(node, selectors)

  implicit class EitherOps(val either: Either[ModifyError, NodeVisitor]) extends AnyVal {
    def apply(field: String): Either[ModifyError, NodeVisitor] = either.map(_.apply(field))

    def apply(index: Int): Either[ModifyError, NodeVisitor] = either.map(_.apply(index))

    def setValue(value: String): Either[ModifyError, Node] =
      either.flatMap(_.modifyValue((_) => value))

    def modifyValue(fn: String => String): Either[ModifyError, Node] =
      either.flatMap(_.modifyValue(fn))

    def removeValue(): Either[ModifyError, Node] = either.flatMap(_.removeValue())
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy