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

zio.config.PropertyTree.scala Maven / Gradle / Ivy

package zio.config

import com.github.ghik.silencer.silent
import zio.config.PropertyTreePath.Step

import scala.collection.immutable.Nil

@silent("Unused import")
sealed trait PropertyTree[+K, +V] { self =>
  import PropertyTree._
  import scala.collection.compat._
  import VersionSpecificSupport._

  def leafNotASequence: PropertyTree[K, V] = self match {
    case Leaf(value, _)     => Leaf(value, canBeSequence = false)
    case Record(value)      => Record(value.map({ case (k, v) => (k, v.leafNotASequence) }))
    case PropertyTree.Empty => PropertyTree.Empty
    case Sequence(value)    => Sequence(value.map(_.leafNotASequence))
  }

  def flatMap[K1 >: K, V1](f: V => PropertyTree[K1, V1]): PropertyTree[K1, V1] =
    self match {
      case Leaf(value, _)     => f(value)
      case Record(value)      => Record(value.map({ case (k, v) => (k.asInstanceOf[K1], v.flatMap(f)) }))
      case PropertyTree.Empty => PropertyTree.Empty
      case Sequence(value)    => Sequence(value.map(_.flatMap(f)))
    }

  def zip[K1 >: K, V1](that: PropertyTree[K1, V1]): PropertyTree[K1, (V, V1)] =
    self.flatMap(t1 => that.map(t2 => (t1, t2)))

  /**
   * `at` allows us to fetch a sub-tree from property-tree
   *
   * Example:
   *
   * {{{
   *
   * Given a config:
   *
   *   {
   *    x : [ a, b, c ]
   *   }
   *
   *   at("x")            // returns Some([a, b, c])
   *   at("x").atIndex(2) // returns Some(Leaf("c"))
   *   at("x").atKey("y") // returns None
   *
   * Similarly, given a more complex config:
   *   {
   *    x : [
   *      {
   *        y1: 1
   *        y2: 2
   *        y3: 3
   *      }
   *      {
   *        y1: 1
   *        y2: 2
   *        y3: 3
   *      }
   *    ]
   *   }
   *
   *   at("x").atIndex(0).atKey("y1") // returns Some(Leaf(v1)
   * }}}
   */
  final def at[K1 >: K](propertyTreePath: PropertyTreePath[K1]): PropertyTree[K1, V] = {
    val steps = propertyTreePath.path

    steps.foldLeft(self.asInstanceOf[PropertyTree[K1, V]]) { (tree, step) =>
      (step match {
        case Step.Index(n) => tree.atIndex(n)
        case Step.Key(k)   => tree.atKey(k)
      }).getOrElse(PropertyTree.empty)
    }
  }

  final def atKey[K1 >: K](key: K1): Option[PropertyTree[K1, V]] =
    self.asInstanceOf[PropertyTree[K1, V]] match {
      case Leaf(_, _)         => None
      case r: Record[K1, V]   => r.value.get(key)
      case PropertyTree.Empty => None
      case Sequence(_)        => None
    }

  final def atIndex[K1 >: K](index: Int): Option[PropertyTree[K1, V]] =
    self match {
      case Leaf(v, bool)      => if (index == 0 && bool) Some(Leaf(v)) else None
      case Record(_)          => None
      case PropertyTree.Empty => None
      case Sequence(value)    => value.lift(index)
    }

  final def flatten[K1 >: K, V1 >: V]: Map[Vector[K1], ::[V1]] = {
    def go(key: Vector[K1], propertyTree: PropertyTree[K1, V], acc: Map[Vector[K1], ::[V1]]): Map[Vector[K1], ::[V1]] =
      propertyTree match {
        case Empty           => acc
        case Sequence(value) => value.foldLeft(acc)((acc, propertyTree) => go(key, propertyTree, acc))
        case Leaf(v, _)      =>
          acc
            .get(key)
            .fold[Map[Vector[K1], ::[V1]]](acc.updated(key, ::(v, Nil)))(value =>
              acc.updated(key, ::(value.head, value.tail :+ v))
            )
        case Record(value)   =>
          value.flatMap(t => go(key :+ t._1, t._2, acc))
      }

    go(Vector.empty, self, Map.empty[Vector[K1], ::[V1]])
  }

  final def flattenKeyAndValue[K1 >: K, V1 >: V](
    pathDelimiter: String = ".",
    valueDelimiter: String = ":"
  )(implicit KS: K1 =:= String): Map[String, String] =
    self
      .flatten[K1, V1]
      .map({ case (key, value) => (key.map(KS).mkString(pathDelimiter), value.mkString(valueDelimiter)) })

  final def flattenKeyWith[K1 >: K, V1 >: V](f: K1 => String)(
    appendPath: String
  ): Map[String, ::[V1]]                             =
    self.flatten[K1, V1].map({ case (key, value) => (key.map(f).mkString(appendPath), value) })

  final def flattenString[K1 >: K, V1 >: V](
    pathDelimiter: String = "."
  )(implicit KS: K1 =:= String): Map[String, ::[V1]] =
    flattenKeyWith[K1, V1](KS)(pathDelimiter)

  final def getOrElse[K1 >: K, V1 >: V](tree: => PropertyTree[K1, V1]): PropertyTree[K1, V1] =
    if (self == PropertyTree.empty) tree else self

  final def getPath[K1 >: K](k: List[K1]): PropertyTree[K1, V] =
    k.foldLeft(self)({ case (node, segment) =>
      node match {
        case Empty | Leaf(_, _) | Sequence(_) => Empty
        case record: Record[K, V]             =>
          record.value.get(segment.asInstanceOf[K]) match {
            case Some(value) => value
            case None        => Empty
          }
      }
    })

  final def isEmpty: Boolean = self match {
    case Empty           => true
    case Leaf(_, _)      => false
    case Record(value)   => value.values.isEmpty
    case Sequence(value) => value.forall(_.isEmpty)
  }

  final def mapKey[K2](f: K => K2): PropertyTree[K2, V] = self match {
    case Leaf(value, bool)  => Leaf(value, bool)
    case Record(value)      => Record(value.map({ case (k, v) => (f(k), v.mapKey(f)) }))
    case PropertyTree.Empty => PropertyTree.Empty
    case Sequence(value)    => Sequence(value.map(_.mapKey(f)))
  }

  final def map[V2](f: V => V2): PropertyTree[K, V2] = self match {
    case Leaf(value, bool) => Leaf(f(value), bool)
    case Record(v)         => Record(v.map { case (k, tree) => (k, tree.map(f)) })
    case Sequence(value)   => Sequence(value.map(_.map(f)))
    case Empty             => Empty
  }

  final def mapEither[E, V2](f: V => Either[E, V2]): Either[E, PropertyTree[K, V2]] = self match {
    case Leaf(value, bool) =>
      f(value).map(Leaf(_, bool))
    case Record(v)         =>
      val map = v.map { case (k, tree) => (k, tree.mapEither(f)) }
      seqMap(map).map(Record(_))

    case Sequence(value) =>
      val list = value.map(_.mapEither(f))
      list
        .foldRight(Right(Nil): Either[E, List[PropertyTree[K, V2]]]) { (acc, a) =>
          acc.flatMap(tree => a.map(list => tree :: list))
        }
        .map(Sequence(_))

    case Empty => Right(Empty)
  }

  def bimap[K2, V2](f: K => K2, g: V => V2): PropertyTree[K2, V2] =
    self.mapKey(f).map(g)

  final def merge[K1 >: K, V1 >: V](that: PropertyTree[K1, V1]): List[PropertyTree[K1, V1]] =
    (self, that) match {
      case (Sequence(l), Sequence(r))           => singleton(Sequence(l ++ r))
      case (l: Record[K, V], r: Record[K1, V1]) =>
        (l.value.keySet ++ r.value.keySet)
          .foldLeft(List[Map[K1, PropertyTree[K1, V1]]](Map.empty)) { case (acc, k) =>
            (l.value.get(k.asInstanceOf[K]), r.value.get(k)) match {
              case (None, None)       => acc
              case (Some(l), Some(r)) =>
                l.merge(r).map(tree => (k, tree)).flatMap(tuple => acc.map(map => map + tuple))
              case (Some(l), None)    => acc.map(map => map + (k -> l))
              case (None, Some(r))    => acc.map(map => map + (k -> r))
            }
          }
          .map(v => PropertyTree.Record(v))
      case (left, right) if left.isEmpty        => singleton(right)
      case (left, right) if right.isEmpty       => singleton(left)
      case (l, r)                               => l :: r :: Nil
    }

  final def nonEmpty: Boolean = !isEmpty
}

object PropertyTree {
  def apply[V](v: V): PropertyTree[Nothing, V] =
    Leaf(v)

  final case class Leaf[V](value: V, canBeSequence: Boolean = true) extends PropertyTree[Nothing, V]

  final case class Record[K, V](value: Map[K, PropertyTree[K, V]]) extends PropertyTree[K, V]

  case object Empty extends PropertyTree[Nothing, Nothing]

  final case class Sequence[K, V](value: List[PropertyTree[K, V]]) extends PropertyTree[K, V]

  val empty: PropertyTree[Nothing, Nothing] = Empty

  def fromMap[K, V](map: Map[K, V]): PropertyTree[K, V] =
    Record(map.map(t => t._1 -> Leaf[V](t._2)))

  def fromStringMap(
    map: Map[String, String],
    keyDelimiter: Option[Char],
    valueDelimiter: Option[Char]
  ): List[PropertyTree[String, String]]                 =
    unflatten(
      map.map { tuple =>
        val vectorOfKeys = keyDelimiter match {
          case Some(keyDelimiter) => tuple._1.split(keyDelimiter).toVector.filterNot(_.trim == "")
          case None               => Vector(tuple._1)
        }
        vectorOfKeys ->
          (valueDelimiter.fold(List(tuple._2))(delim =>
            tuple._2
              .split(delim)
              .toList
          ) match {
            case h :: tail =>
              ::(h, tail)
            case Nil       => singleton(tuple._2)
          })
      }
    )

  def mergeAll[K, V](list: List[PropertyTree[K, V]]): List[PropertyTree[K, V]] =
    list.reverse match {
      case Nil          => Nil
      case head :: tail =>
        tail.foldLeft(List(head)) { case (acc, tree) =>
          acc.flatMap(tree0 => tree.merge(tree0))
        }
    }

  def unflatten[K, V](key: List[K], value: ::[V]): PropertyTree[K, V] =
    unflatten(key, Sequence(value.map(Leaf(_))))

  def unflatten[K, V](key: List[K], tree: PropertyTree[K, V]): PropertyTree[K, V] =
    key match {
      case ::(head, next) => Record(Map(head -> unflatten(next, tree)))
      case Nil            => tree
    }

  def unflatten[K, V](map: Map[Vector[K], ::[V]]): List[PropertyTree[K, V]] =
    mergeAll(map.toList.map(tuple => unflatten(tuple._1.toList, tuple._2)))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy