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

zio.test.render.LogLine.scala Maven / Gradle / Ivy

/*
 * Copyright 2019-2024 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.test.render

import zio.stacktracer.TracingImplicits.disableAutoTrace
import zio.test.render.LogLine.Fragment.Style

object LogLine {
  case class Message(lines: Vector[Line] = Vector.empty) {

    def +(fragment: Fragment) =
      this +++ Message(Vector(Line(Vector(fragment))))

    def +:(line: Line) = Message(line +: lines)
    def :+(fragment: Fragment): Message = Message(lines match {
      case line +: lines => (fragment +: line) +: lines
      case _             => Vector(fragment.toLine)
    })
    def :+(line: Line)       = Message(lines :+ line)
    def ++(message: Message) = Message(lines ++ message.lines)
    def +++(message: Message) =
      (lines.lastOption, message.lines.headOption) match {
        case (Some(last), Some(head)) =>
          Message(lines.dropRight(1) :+ (last ++ head)) ++ Message(message.lines.drop(1))
        case _ =>
          this ++ message
      }
    def drop(n: Int): Message            = Message(lines.drop(n))
    def map(f: Line => Line): Message    = Message(lines = lines.map(f))
    def withOffset(offset: Int): Message = Message(lines.map(_.withOffset(offset)))
    def intersperse(line: Line): Message = Message(
      lines.foldRight(List.empty[Line]) { case (ln, rest) =>
        ln :: (if (rest.isEmpty) Nil else line :: rest)
      }
    )
  }
  object Message {
    def apply(lines: Seq[Line]): Message = Message(lines.toVector)
    def apply(lineText: String): Message = Fragment(lineText).toLine.toMessage
    val empty: Message                   = Message()
  }
  case class Line(fragments: Vector[Fragment] = Vector.empty, offset: Int = 0) { self =>
    def +:(fragment: Fragment): Line       = Line(fragment +: fragments)
    def :+(fragment: Fragment): Line       = Line(fragments :+ fragment)
    def +(fragment: Fragment): Line        = Line(fragments :+ fragment)
    def prepend(message: Message): Message = Message(this +: message.lines)
    def +(line: Line): Message             = Message(Vector(this, line))
    def ++(line: Line): Line               = copy(fragments = fragments ++ line.fragments)
    def withOffset(shift: Int): Line       = copy(offset = offset + shift)
    def toMessage: Message                 = Message(Vector(this))

    def optimized: Line = copy(fragments = optimize(fragments))
    private def optimize(fragments: Vector[Fragment]) =
      fragments
        .foldRight(List.empty[Fragment]) { case (curr, rest) =>
          rest match {
            case next :: fs =>
              if (curr.style == next.style) curr.copy(text = curr.text + next.text) :: fs
              else curr :: next :: fs
            case Nil => curr :: Nil
          }
        }
        .toVector
  }
  object Line {
    def fromString(text: String, offset: Int = 0): Line = Fragment(text).toLine.withOffset(offset)
    val empty: Line                                     = Line()
  }
  case class Fragment(text: String, style: Style = Style.Default) { self =>
    def +:(line: Line)            = prepend(line)
    def prepend(line: Line): Line = Line(this +: line.fragments, line.offset)
    def +(f: Fragment)            = Line(Vector(this, f))
    def toLine: Line              = Line(Vector(this))
    def *(n: Int)                 = copy(text = text * n)

    def bold: Fragment =
      copy(style = Style.Bold(self))

    def underlined: Fragment =
      copy(style = Style.Underlined(self))

    def ansi(ansiColor: String): Fragment =
      copy(style = Style.Ansi(self, ansiColor))
  }
  object Fragment {
    sealed trait Style
    object Style {
      case object Primary                                    extends Style
      case object Default                                    extends Style
      case object Warning                                    extends Style
      case object Error                                      extends Style
      case object Info                                       extends Style
      case object Detail                                     extends Style
      case object Dimmed                                     extends Style
      final case class Bold(fr: Fragment)                    extends Style
      final case class Underlined(fr: Fragment)              extends Style
      final case class Ansi(fr: Fragment, ansiColor: String) extends Style
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy