diff --git a/build.sbt b/build.sbt index 1263136..0637891 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ import xerial.sbt.Sonatype.sonatypeCentralHost -ThisBuild / scalaVersion := "3.5.0" +ThisBuild / scalaVersion := "3.6.2" ThisBuild / licenses := List(("MIT", url("https://opensource.org/licenses/MIT"))) ThisBuild / homepage := Some(url("https://github.com/d10xa/json-log-viewer")) ThisBuild / organization := "ru.d10xa" @@ -17,6 +17,14 @@ ThisBuild / sonatypeCredentialHost := sonatypeCentralHost val circeVersion = "0.14.10" val declineVersion = "2.4.1" val fs2Version = "3.11.0" +val munitVersion = "1.0.3" + +lazy val root = project.in(file(".")). + aggregate(`json-log-viewer`.js, `json-log-viewer`.jvm, `frontend-laminar`). + settings( + publish := {}, + publishLocal := {}, + ) lazy val `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) .in(file("json-log-viewer")) @@ -28,6 +36,7 @@ lazy val `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) pomIncludeRepository := { _ => false }, libraryDependencies ++= Seq( "org.typelevel" %%% "cats-effect" % "3.5.4", + "org.typelevel" %%% "munit-cats-effect" % "2.0.0" % Test, "co.fs2" %%% "fs2-core" % fs2Version, "co.fs2" %%% "fs2-io" % fs2Version, "com.monovore" %%% "decline" % declineVersion, @@ -36,20 +45,22 @@ lazy val `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) "io.circe" %%% "circe-literal" % circeVersion % Test, "io.circe" %%% "circe-parser" % circeVersion, "io.circe" %%% "circe-generic" % circeVersion, - "io.circe" %%% "circe-yaml-scalayaml" % "0.16.0", "com.lihaoyi" %%% "fansi" % "0.5.0", "org.scala-lang.modules" %%% "scala-parser-combinators" % "2.4.0", - "org.scalameta" %% "munit" % "0.7.29" % Test + "org.scalameta" %%% "munit" % munitVersion % Test ), fork := true, run / connectInput := true, ) .jvmSettings( + libraryDependencies ++= Seq( + "io.circe" %%% "circe-yaml-scalayaml" % "0.16.0" + ), publish / skip := false ) .jsSettings( publish / skip := true, - fork := false, + fork := false ) lazy val `make-logs` = project @@ -77,12 +88,13 @@ lazy val `frontend-laminar` = project "com.raquo" %%% "airstream" % "16.0.0", "com.raquo" %%% "waypoint" % "7.0.0", "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.28.4", - "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.28.4" % "provided" + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.28.4" % "provided", + "org.scalameta" %% "munit" % munitVersion % Test ), Compile / fastLinkJS / scalaJSLinkerConfig ~= { _.withSourceMap(true) }, Compile / fullLinkJS / scalaJSLinkerConfig ~= { _.withSourceMap(true) }, scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, + scalaJSUseMainModuleInitializer := true, - (Test / requireJsDomEnv) := true, - useYarn := true + (Test / requireJsDomEnv) := true ) diff --git a/frontend-laminar/src/main/resources/index-fastopt.html b/frontend-laminar/src/main/resources/index-fastopt.html index 8295338..6871b8c 100644 --- a/frontend-laminar/src/main/resources/index-fastopt.html +++ b/frontend-laminar/src/main/resources/index-fastopt.html @@ -7,7 +7,7 @@ - +
diff --git a/frontend-laminar/src/main/resources/index-fullopt.html b/frontend-laminar/src/main/resources/index-fullopt.html index 8e5b39d..a59e18f 100644 --- a/frontend-laminar/src/main/resources/index-fullopt.html +++ b/frontend-laminar/src/main/resources/index-fullopt.html @@ -5,7 +5,7 @@ [FULL] json-log-viewer - +
diff --git a/frontend-laminar/src/main/scala/ViewElement.scala b/frontend-laminar/src/main/scala/ViewElement.scala deleted file mode 100644 index 80fc995..0000000 --- a/frontend-laminar/src/main/scala/ViewElement.scala +++ /dev/null @@ -1,62 +0,0 @@ -import App.textVar -import com.monovore.decline.Help -import com.raquo.airstream.core.Signal -import com.raquo.laminar.DomApi -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.api.L -import ru.d10xa.jsonlogviewer.Application -import ru.d10xa.jsonlogviewer.JsonDetector -import ru.d10xa.jsonlogviewer.JsonPrefixPostfix -import ru.d10xa.jsonlogviewer.JsonLogLineParser -import ru.d10xa.jsonlogviewer.TimestampFilter -import ru.d10xa.jsonlogviewer.LogViewerStream -import ru.d10xa.jsonlogviewer.LogLineFilter -import fs2.* -import ru.d10xa.jsonlogviewer.decline.Config.FormatIn -import ru.d10xa.jsonlogviewer.decline.Config -import ru.d10xa.jsonlogviewer.decline.Config -import ru.d10xa.jsonlogviewer.decline.TimestampConfig -import ru.d10xa.jsonlogviewer.decline.TimestampConfig -import ru.d10xa.jsonlogviewer.formatout.ColorLineFormatter -import ru.d10xa.jsonlogviewer.logfmt.LogfmtLogLineParser - -import scala.util.chaining.scalaUtilChainingOps -object ViewElement { - - def runApp( - logLinesSignal: Signal[String], - configSignal: Signal[Either[Help, Config]] - ): Signal[HtmlElement] = - logLinesSignal.combineWith(configSignal).map { - case (string, Right(c)) => - val jsonPrefixPostfix = JsonPrefixPostfix(JsonDetector()) - val logLineParser = c.formatIn match - case Some(FormatIn.Logfmt) => LogfmtLogLineParser(c) - case Some(FormatIn.Json) => JsonLogLineParser(c, jsonPrefixPostfix) - case other => - throw new IllegalStateException( - s"Unsupported format-in value: $other" - ) - - fs2.Stream - .emits(string.split("\n")) - .filter(_.trim.nonEmpty) - .through(LogViewerStream.stream(c, logLineParser)) - .map(Ansi2HtmlWithClasses.apply) - .toList - .mkString("
", "", "
") - .pipe(DomApi.unsafeParseHtmlString) - .pipe(foreignHtmlElement) - case (string, Left(help)) => - pre(cls := "text-light", help.toString) - } - def render( - logLinesSignal: Signal[String], - configSignal: Signal[Either[Help, Config]] - ): HtmlElement = - pre( - cls := "bg-dark font-monospace text-white", - child <-- runApp(logLinesSignal, configSignal) - ) - -} diff --git a/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala similarity index 96% rename from frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala index d0701e3..3b71760 100644 --- a/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala @@ -1,3 +1,5 @@ +package ru.d10xa.jsonlogviewer + import fansi.ErrorMode import fansi.Str @@ -6,7 +8,7 @@ import scala.scalajs.js.annotation.JSExport /** https://github.com/com-lihaoyi/fansi/issues/62 */ object Ansi2HtmlWithClasses extends Function1[String, String]: - private def transition(from: fansi.Attr, to: fansi.Attr) = + def transition(from: fansi.Attr, to: fansi.Attr): String = import fansi.* (from, to) match case (Underlined.Off, Underlined.On) => "" diff --git a/frontend-laminar/src/main/scala/App.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala similarity index 91% rename from frontend-laminar/src/main/scala/App.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala index f7dca2d..a46886d 100644 --- a/frontend-laminar/src/main/scala/App.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala @@ -1,20 +1,25 @@ -import Router0.* +package ru.d10xa.jsonlogviewer + import com.monovore.decline.Help -import com.raquo.laminar.DomApi import com.raquo.laminar.api.L import com.raquo.laminar.api.L.* import com.raquo.laminar.nodes.ReactiveHtmlElement import com.raquo.waypoint.* -import fansi.ErrorMode import org.scalajs.dom import org.scalajs.dom.HTMLButtonElement import org.scalajs.dom.HTMLDivElement +import ru.d10xa.jsonlogviewer.Router0.* +import ru.d10xa.jsonlogviewer.Router0.EditPage +import ru.d10xa.jsonlogviewer.Router0.HelpPage +import ru.d10xa.jsonlogviewer.Router0.LivePage +import ru.d10xa.jsonlogviewer.Router0.Page +import ru.d10xa.jsonlogviewer.Router0.ViewPage +import ru.d10xa.jsonlogviewer.Router0.navigateTo +import ru.d10xa.jsonlogviewer.decline.Config import ru.d10xa.jsonlogviewer.decline.Config.FormatIn -import ru.d10xa.jsonlogviewer.decline.Config.FormatOut import ru.d10xa.jsonlogviewer.decline.Config.FormatIn.Json import ru.d10xa.jsonlogviewer.decline.Config.FormatIn.Logfmt -import ru.d10xa.jsonlogviewer.decline.Config -import ru.d10xa.jsonlogviewer.decline.Config +import ru.d10xa.jsonlogviewer.decline.Config.FormatOut import ru.d10xa.jsonlogviewer.decline.DeclineOpts import ru.d10xa.jsonlogviewer.query.QueryCompiler @@ -50,7 +55,7 @@ object App { val formatInVar: Var[FormatIn] = Var( FormatIn.Json ) - val formatOutVar: Var[FormatOut] = Var( + val formatOutVar: Var[FormatOut] = Var( FormatOut.Pretty ) @@ -71,7 +76,13 @@ object App { case Right(value) => Some(value) } yield DeclineOpts.command .parse(splitArgs(cli)) - .map(cfg => cfg.copy(filter = filter, formatIn = Some(formatIn), formatOut = Some(formatOut))) + .map(cfg => + cfg.copy( + filter = filter, + formatIn = Some(formatIn), + formatOut = Some(formatOut) + ) + ) def main(args: Array[String]): Unit = { lazy val container = dom.document.getElementById("app-container") @@ -135,11 +146,11 @@ object App { select( cls := "col-1", value <-- formatOutVar.signal.map { - case FormatOut.Raw => "raw" + case FormatOut.Raw => "raw" case FormatOut.Pretty => "pretty" }, onChange.mapToValue.map { - case "raw" => FormatOut.Raw + case "raw" => FormatOut.Raw case "pretty" => FormatOut.Pretty } --> formatOutVar, option(value := "pretty", "pretty"), @@ -158,7 +169,7 @@ object App { onInput.mapToValue --> filterVar ) ) -) + ) def additionalArgsDiv: ReactiveHtmlElement[HTMLDivElement] = div( cls := "row-fluid", @@ -193,6 +204,7 @@ object App { } ) private def renderLivePage(): HtmlElement = { + implicit val owner: Owner = new Owner {} div( formatInDiv, formatOutDiv, @@ -205,6 +217,7 @@ object App { } private def renderViewPage(): HtmlElement = { + implicit val owner: Owner = new Owner {} div( ViewElement.render(textVar.signal, configSignal) ) diff --git a/frontend-laminar/src/main/scala/EditElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala similarity index 78% rename from frontend-laminar/src/main/scala/EditElement.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala index 46a703c..ae9f6df 100644 --- a/frontend-laminar/src/main/scala/EditElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala @@ -1,5 +1,7 @@ -import com.raquo.laminar.api.L.{*, given} +package ru.d10xa.jsonlogviewer + import com.raquo.laminar.api.L +import com.raquo.laminar.api.L.* object EditElement { def render(mods: Modifier[TextArea]*): HtmlElement = diff --git a/frontend-laminar/src/main/scala/Router0.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala similarity index 97% rename from frontend-laminar/src/main/scala/Router0.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala index eb17fab..96155dd 100644 --- a/frontend-laminar/src/main/scala/Router0.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala @@ -1,8 +1,10 @@ -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.* -import org.scalajs.dom +package ru.d10xa.jsonlogviewer + import com.github.plokhotnyuk.jsoniter_scala.core.* import com.github.plokhotnyuk.jsoniter_scala.macros.* +import com.raquo.laminar.api.L.* +import com.raquo.waypoint.* +import org.scalajs.dom object Router0 { diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala new file mode 100644 index 0000000..2a3a8cc --- /dev/null +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -0,0 +1,74 @@ +package ru.d10xa.jsonlogviewer + +import cats.effect.IO +import cats.effect.unsafe.implicits.global +import com.monovore.decline.Help +import com.raquo.airstream.core.Signal +import com.raquo.airstream.eventbus.EventBus +import com.raquo.airstream.ownership.Owner +import com.raquo.laminar.DomApi +import com.raquo.laminar.api.L.* +import ru.d10xa.jsonlogviewer.decline.Config +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml +import ru.d10xa.jsonlogviewer.decline.yaml.Feed + +import scala.util.chaining.* + +object ViewElement { + + def stringsToHtmlElement(strings: List[String]): HtmlElement = + strings + .map(Ansi2HtmlWithClasses.apply) + .mkString("
", "", "
") + .pipe(DomApi.unsafeParseHtmlString) + .pipe(foreignHtmlElement) + + def modifyConfigForInlineInput(string: String, config: Config): Config = + config.copy(configYaml = + Some( + ConfigYaml( + filter = None, + formatIn = None, + commands = None, + feeds = Some( + List( + Feed( + name = None, + commands = List.empty, + inlineInput = Some(string), + filter = config.filter, + formatIn = config.formatIn + ) + ) + ) + ) + ) + ) + + def render( + logLinesSignal: Signal[String], + configSignal: Signal[Either[Help, Config]] + )(implicit owner: Owner): HtmlElement = { + val eventBus = new EventBus[HtmlElement] + logLinesSignal + .combineWith(configSignal) + .foreach { + case (string, Right(c)) => + LogViewerStream + .stream(modifyConfigForInlineInput(string, c)) + .compile + .toList + .map(stringsToHtmlElement) + .flatMap(e => IO(eventBus.writer.onNext(e))) + .unsafeRunAndForget() + + case (_, Left(help)) => + eventBus.writer.onNext(pre(cls := "text-light", help.toString)) + }(owner) + + pre( + cls := "bg-dark font-monospace text-white", + child <-- eventBus.events + ) + } +} diff --git a/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala new file mode 100644 index 0000000..f32e792 --- /dev/null +++ b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala @@ -0,0 +1,44 @@ +package ru.d10xa.jsonlogviewer + +import fansi.Attr +import fansi.Bold +import fansi.Color +import fansi.Underlined +import munit.FunSuite + +class Ansi2HtmlWithClassesTest extends FunSuite { + + test("transition correctly handles Bold.On to Bold.Off") { + val from = Bold.On + val to = Bold.Off + val result = Ansi2HtmlWithClasses.transition(from, to) + assertEquals(result, "") + } + + test("transition correctly handles Underlined.Off to Underlined.On") { + val from = Underlined.Off + val to = Underlined.On + val result = Ansi2HtmlWithClasses.transition(from, to) + assertEquals(result, "") + } + + test("transition returns empty string for unmatched attributes") { + val from = Bold.Off + val to = Color.Reset + val result = Ansi2HtmlWithClasses.transition(from, to) + assertEquals(result, "") + } + + test("apply correctly handles a simple ANSI string") { + val input = "\u001b[1mBold\u001b[0m" + val result = Ansi2HtmlWithClasses.apply(input) + assert(result.contains("Bold"), "Expected bold tags around 'Bold'") + } + + test("apply handles empty string gracefully") { + val input = "" + val result = Ansi2HtmlWithClasses.apply(input) + assertEquals(result, "") + } + +} diff --git a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala new file mode 100644 index 0000000..888bdf4 --- /dev/null +++ b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala @@ -0,0 +1,7 @@ +package ru.d10xa.jsonlogviewer +import cats.effect.IO + +class StdInLinesStreamImpl extends StdInLinesStream { + + override def stdinLinesStream: fs2.Stream[IO, String] = fs2.Stream.empty +} diff --git a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala index 6d55988..811d241 100644 --- a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala +++ b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala @@ -1,9 +1,5 @@ package ru.d10xa.jsonlogviewer.decline -import cats.data.Validated -import cats.data.Validated -import cats.effect.IO -import cats.effect.IO import cats.effect.IO import ru.d10xa.jsonlogviewer.decline.Config.FormatIn diff --git a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala index 553ec5d..c2cce38 100644 --- a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala +++ b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala @@ -1,12 +1,16 @@ package ru.d10xa.jsonlogviewer.shell -import cats.effect.* +import cats.effect.IO import fs2.* -import java.io.* +class ShellImpl extends Shell { -class ShellImpl[F[_]] extends Shell[F] { - - def mergeCommands(commands: List[String]): Stream[F, String] = Stream.empty + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): Stream[IO, String] = inlineInput match + case Some(inlineInput) => + Shell.stringToStream(inlineInput) + case None => Stream.empty } diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala new file mode 100644 index 0000000..1593f8b --- /dev/null +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala @@ -0,0 +1,13 @@ +package ru.d10xa.jsonlogviewer +import cats.effect.IO +import fs2.Chunk +import fs2.Stream +import fs2.io.stdinUtf8 + +class StdInLinesStreamImpl extends StdInLinesStream { + + override def stdinLinesStream: Stream[IO, String] = + stdinUtf8[IO](1024 * 1024 * 10) + .repartition(s => Chunk.array(s.split("\n", -1))) + .filter(_.nonEmpty) +} diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala index c240d8e..c28aa13 100644 --- a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/ConfigInitImpl.scala @@ -2,10 +2,12 @@ package ru.d10xa.jsonlogviewer.decline import cats.data.Validated import cats.effect.IO -import ru.d10xa.jsonlogviewer.ConfigYamlReader import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import cats.syntax.all.* import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoader +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoaderImpl +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlReader import java.io.File diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala new file mode 100644 index 0000000..77b93c7 --- /dev/null +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala @@ -0,0 +1,7 @@ +package ru.d10xa.jsonlogviewer.decline.yaml + +import cats.data.ValidatedNel + +trait ConfigYamlLoader { + def parseYamlFile(content: String): ValidatedNel[String, ConfigYaml] +} diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala similarity index 88% rename from json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala rename to json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala index 086817b..bf08d35 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -10,10 +10,9 @@ import io.circe.yaml.scalayaml.parser import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.decline.FormatInValidator import ru.d10xa.jsonlogviewer.decline.QueryASTValidator -import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml import ru.d10xa.jsonlogviewer.query.QueryAST -object ConfigYamlLoader { +class ConfigYamlLoaderImpl extends ConfigYamlLoader { private def trimCommentedLines(str: String): String = str.linesIterator .filterNot(line => @@ -117,22 +116,42 @@ object ConfigYamlLoader { Validated.invalidNel(s"Missing '$fieldName' field in feed") } + private def parseOptionalString( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[String]] = + fields.get(fieldName) match { + case Some(c) => + c.as[Option[String]] + .leftMap(_ => s"Invalid '$fieldName' field in feed") + .toValidatedNel + case None => + Validated.valid(None) + } + private def parseFeed(feedJson: Json): ValidatedNel[String, Feed] = feedJson.asObject.map(_.toMap) match { case None => Validated.invalidNel("Feed entry is not a valid JSON object") case Some(feedFields) => - val nameValidated = parseString( + val nameValidated = parseOptionalString( feedFields, - "name", - "Invalid 'name' field in feed" + "name" ) val commandsValidated = parseListString(feedFields, "commands") + val inlineInputValidated = + parseOptionalString(feedFields, "inlineInput") val filterValidated = parseOptionalQueryAST(feedFields, "filter") val formatInValidated : Validated[NonEmptyList[String], Option[FormatIn]] = parseOptionalFormatIn(feedFields, "formatIn") - (nameValidated, commandsValidated, filterValidated, formatInValidated) + ( + nameValidated, + commandsValidated, + inlineInputValidated, + filterValidated, + formatInValidated + ) .mapN(Feed.apply) } diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ConfigYamlReader.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlReader.scala similarity index 74% rename from json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ConfigYamlReader.scala rename to json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlReader.scala index 9d91e90..380c46a 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ConfigYamlReader.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlReader.scala @@ -1,14 +1,14 @@ -package ru.d10xa.jsonlogviewer +package ru.d10xa.jsonlogviewer.decline.yaml -import cats.effect.IO import cats.data.ValidatedNel +import cats.effect.IO import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoader import scala.io.Source object ConfigYamlReader { - + val configYamlLoader: ConfigYamlLoader = ConfigYamlLoaderImpl() def readFile(filePath: String): IO[String] = IO.blocking(Source.fromFile(filePath)) .bracket { source => @@ -18,5 +18,5 @@ object ConfigYamlReader { } def fromYamlFile(filePath: String): IO[ValidatedNel[String, ConfigYaml]] = - readFile(filePath).map(ConfigYamlLoader.parseYamlFile) + readFile(filePath).map(configYamlLoader.parseYamlFile) } diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala index 180571d..65be47f 100644 --- a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/shell/ShellImpl.scala @@ -3,33 +3,38 @@ package ru.d10xa.jsonlogviewer.shell import cats.effect.* import cats.syntax.all.* -class ShellImpl[F[_]: Async] extends Shell[F] { +class ShellImpl extends Shell { - def createProcess(command: String): Resource[F, Process] = - Resource.make(Async[F].delay { + def createProcess(command: String): Resource[IO, Process] = + Resource.make(IO { new ProcessBuilder("sh", "-c", command) .redirectErrorStream(true) .start() - })(process => Async[F].delay(process.destroy())) + })(process => IO(process.destroy())) - def runInfiniteCommand(command: String): fs2.Stream[F, String] = + private def runInfiniteCommand(command: String): fs2.Stream[IO, String] = fs2.Stream.resource(createProcess(command)).flatMap { process => fs2.io .readInputStream( - Async[F].delay(process.getInputStream), + IO(process.getInputStream), 4096, closeAfterUse = false ) .through(fs2.text.utf8.decode) .through(fs2.text.lines) - .onFinalize(Async[F].delay { + .onFinalize(IO { process.waitFor() }.void) } - def mergeCommands(commands: List[String]): fs2.Stream[F, String] = { - val streams = commands.map(runInfiniteCommand) + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): fs2.Stream[IO, String] = { + val streams = + commands + .map(runInfiniteCommand) ++ inlineInput.map(Shell.stringToStream).toList fs2.Stream.emits(streams).parJoin(math.max(1, commands.length)) } -} \ No newline at end of file +} diff --git a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala b/json-log-viewer/jvm/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala similarity index 83% rename from json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala rename to json-log-viewer/jvm/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala index 3078b61..3375e79 100644 --- a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala +++ b/json-log-viewer/jvm/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala @@ -3,10 +3,14 @@ package ru.d10xa.jsonlogviewer.decline.yaml import cats.data.Validated import munit.FunSuite import ru.d10xa.jsonlogviewer.decline.Config.FormatIn +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoader +import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlLoaderImpl import ru.d10xa.jsonlogviewer.query.QueryAST class ConfigYamlLoaderTest extends FunSuite { + private val configYamlLoader: ConfigYamlLoader = new ConfigYamlLoaderImpl + test("parse valid yaml with feeds") { val yaml = """|commands: @@ -31,7 +35,7 @@ class ConfigYamlLoaderTest extends FunSuite { | formatIn: logfmt |""".stripMargin - val result = ConfigYamlLoader.parseYamlFile(yaml) + val result = configYamlLoader.parseYamlFile(yaml) assert(result.isValid, s"Result should be valid: $result") val config = result.toOption.get @@ -46,7 +50,7 @@ class ConfigYamlLoaderTest extends FunSuite { assertEquals(feeds.size, 2) val feed1 = feeds.head - assertEquals(feed1.name, "pod-logs") + assertEquals(feed1.name, Some("pod-logs")) assertEquals( feed1.commands, List("./mock-logs.sh pod1", "./mock-logs.sh pod2") @@ -54,14 +58,14 @@ class ConfigYamlLoaderTest extends FunSuite { assertEquals(feed1.formatIn, Some(FormatIn.Json)) val feed2 = feeds(1) - assertEquals(feed2.name, "service-logs") + assertEquals(feed2.name, Some("service-logs")) assertEquals(feed2.commands, List("./mock-logs.sh service1")) assertEquals(feed2.formatIn, Some(FormatIn.Logfmt)) } test("parse empty yaml") { val yaml = "" - val result = ConfigYamlLoader.parseYamlFile(yaml) + val result = configYamlLoader.parseYamlFile(yaml) assert(result.isValid, s"Result should be valid for empty yaml: $result") val config = result.toOption.get @@ -76,7 +80,7 @@ class ConfigYamlLoaderTest extends FunSuite { """formatIn: | - not a string |""".stripMargin - val result = ConfigYamlLoader.parseYamlFile(yaml) + val result = configYamlLoader.parseYamlFile(yaml) assert(result.isInvalid, s"Result should be invalid: $result") val errors = result.swap.toOption.get diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala index a104752..14e01bb 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala @@ -4,15 +4,9 @@ import cats.effect.* import com.monovore.decline.Opts import com.monovore.decline.effect.CommandIOApp import fs2.* -import fs2.io.* -import ru.d10xa.jsonlogviewer.decline.Config -import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.decline.ConfigInit import ru.d10xa.jsonlogviewer.decline.ConfigInitImpl import ru.d10xa.jsonlogviewer.decline.DeclineOpts -import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml -import ru.d10xa.jsonlogviewer.logfmt.LogfmtLogLineParser -import ru.d10xa.jsonlogviewer.shell.ShellImpl object Application extends CommandIOApp( @@ -26,7 +20,7 @@ object Application configInit.initConfig(c).flatMap { updatedConfig => IO { LogViewerStream - .stream[IO](updatedConfig) + .stream(updatedConfig) .through(text.utf8.encode) .through(io.stdout) .compile diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala index d75e472..3e7277c 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala @@ -1,9 +1,10 @@ package ru.d10xa.jsonlogviewer -import cats.effect.Async +import cats.effect.IO import cats.syntax.all.* import fs2.* import fs2.io.* +import ru.d10xa.jsonlogviewer.StdInLinesStreamImpl import ru.d10xa.jsonlogviewer.decline.Config import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.decline.ConfigInit @@ -30,24 +31,23 @@ object LogViewerStream { } } - private def commandsToStream[F[_]: Async]( - commands: List[String] - ): Stream[F, String] = { - new ShellImpl[F]().mergeCommands(commands) + private def commandsAndInlineInputToStream( + commands: List[String], + inlineInput: Option[String] + ): Stream[IO, String] = { + new ShellImpl().mergeCommandsAndInlineInput(commands, inlineInput) } - private def stdinLinesStream[F[_]: Async]: Stream[F, String] = - stdinUtf8[F](1024 * 1024 * 10) - .repartition(s => Chunk.array(s.split("\n", -1))) - .filter(_.nonEmpty) + private val stdinLinesStream: Stream[IO, String] = + new StdInLinesStreamImpl().stdinLinesStream - private def processStream[F[_]: Async]( + private def processStream( baseConfig: Config, - lines: Stream[F, String], + lines: Stream[IO, String], feedFilter: Option[QueryAST], feedFormatIn: Option[FormatIn], feedName: Option[String] - ): Stream[F, String] = { + ): Stream[IO, String] = { val effectiveFormatIn = feedFormatIn.orElse(baseConfig.formatIn) val effectiveFilter = feedFilter.orElse(baseConfig.filter) val effectiveConfig = baseConfig.copy( @@ -65,14 +65,16 @@ object LogViewerStream { ColorLineFormatter(effectiveConfig, feedName) lines - .map(logLineParser.parse) + .map { it => + logLineParser.parse(it) + } .filter(logLineFilter.grep) .filter(logLineFilter.logLineQueryPredicate) .through( - timestampFilter.filterTimestampAfter[F](effectiveConfig.timestamp.after) + timestampFilter.filterTimestampAfter(effectiveConfig.timestamp.after) ) .through( - timestampFilter.filterTimestampBefore[F]( + timestampFilter.filterTimestampBefore( effectiveConfig.timestamp.before ) ) @@ -80,30 +82,34 @@ object LogViewerStream { .map(_.toString) } - def stream[F[_]: Async](config: Config): Stream[F, String] = { + def stream(config: Config): Stream[IO, String] = { val topCommandsOpt: Option[List[String]] = config.configYaml.flatMap(_.commands).filter(_.nonEmpty) + val feedsOpt: Option[List[Feed]] = config.configYaml.flatMap(_.feeds).filter(_.nonEmpty) val finalStream = feedsOpt match { case Some(feeds) => val feedStreams = feeds.map { feed => - val feedStream = commandsToStream[F](feed.commands) + val feedStream: Stream[IO, String] = + commandsAndInlineInputToStream(feed.commands, feed.inlineInput) processStream( config, feedStream, feed.filter, feed.formatIn, - feed.name.some + feed.name ) } Stream.emits(feedStreams).parJoin(feedStreams.size) case None => val baseStream = topCommandsOpt match { - case Some(cmds) => commandsToStream[F](cmds) - case None => stdinLinesStream[F] + case Some(cmds) => + commandsAndInlineInputToStream(cmds, None) + case None => + stdinLinesStream } processStream(config, baseStream, None, None, None) } diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ParseResultKeys.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ParseResultKeys.scala index 8766ccc..d4cbe75 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ParseResultKeys.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/ParseResultKeys.scala @@ -6,6 +6,7 @@ import HardcodedFieldNames.loggerNameFieldName import HardcodedFieldNames.threadNameFieldName import HardcodedFieldNames.stackTraceFieldName import ru.d10xa.jsonlogviewer.decline.Config + class ParseResultKeys(config: Config) { def getByKey( parseResult: ParseResult, diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStream.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStream.scala new file mode 100644 index 0000000..c859967 --- /dev/null +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStream.scala @@ -0,0 +1,6 @@ +package ru.d10xa.jsonlogviewer +import cats.effect.IO + +trait StdInLinesStream { + def stdinLinesStream: fs2.Stream[IO, String] +} diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/TimestampFilter.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/TimestampFilter.scala index c610974..6b8bd5a 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/TimestampFilter.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/TimestampFilter.scala @@ -1,23 +1,24 @@ package ru.d10xa.jsonlogviewer +import cats.effect.IO import fs2.Pipe import java.time.ZonedDateTime class TimestampFilter: - def filterTimestampAfter[F[_]]( + def filterTimestampAfter( t: Option[ZonedDateTime] - ): Pipe[F, ParseResult, ParseResult] = filterTimestamp(t, _.isAfter(_)) + ): Pipe[IO, ParseResult, ParseResult] = filterTimestamp(t, _.isAfter(_)) - def filterTimestampBefore[F[_]]( + def filterTimestampBefore( t: Option[ZonedDateTime] - ): Pipe[F, ParseResult, ParseResult] = filterTimestamp(t, _.isBefore(_)) + ): Pipe[IO, ParseResult, ParseResult] = filterTimestamp(t, _.isBefore(_)) - def filterTimestamp[F[_]]( + def filterTimestamp( t: Option[ZonedDateTime], predicate: (ZonedDateTime, ZonedDateTime) => Boolean - ): Pipe[F, ParseResult, ParseResult] = + ): Pipe[IO, ParseResult, ParseResult] = p => t match case Some(valueFromRequest) => diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala index 3d10b44..2fecfc7 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala @@ -1,9 +1,6 @@ package ru.d10xa.jsonlogviewer.decline -import ru.d10xa.jsonlogviewer.decline.Config import ru.d10xa.jsonlogviewer.decline.Config.ConfigGrep -import ru.d10xa.jsonlogviewer.decline.ConfigFile -import ru.d10xa.jsonlogviewer.decline.TimestampConfig import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml import ru.d10xa.jsonlogviewer.query.QueryAST @@ -16,7 +13,7 @@ final case class Config( grep: List[ConfigGrep], filter: Option[QueryAST], formatIn: Option[Config.FormatIn], - formatOut: Option[Config.FormatOut], + formatOut: Option[Config.FormatOut] ) object Config: @@ -24,7 +21,7 @@ object Config: enum FormatIn: case Json, Logfmt - + enum FormatOut: case Pretty, Raw diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/DeclineOpts.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/DeclineOpts.scala index 76b5ab4..03f7a0b 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/DeclineOpts.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/DeclineOpts.scala @@ -11,7 +11,6 @@ import ru.d10xa.jsonlogviewer.decline.Config.ConfigGrep import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.decline.Config.FormatOut import ru.d10xa.jsonlogviewer.query.QueryAST -import ru.d10xa.jsonlogviewer.query.QueryCompiler import java.time.ZonedDateTime diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatInValidator.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatInValidator.scala index 808c56b..de33361 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatInValidator.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatInValidator.scala @@ -2,7 +2,6 @@ package ru.d10xa.jsonlogviewer.decline import cats.data.NonEmptyList import cats.data.Validated -import cats.data.ValidatedNel import ru.d10xa.jsonlogviewer.decline.Config.FormatIn object FormatInValidator { diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatOutValidator.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatOutValidator.scala index c86966b..f6dcf7d 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatOutValidator.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatOutValidator.scala @@ -2,15 +2,13 @@ package ru.d10xa.jsonlogviewer.decline import cats.data.NonEmptyList import cats.data.Validated -import cats.data.ValidatedNel -import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.decline.Config.FormatOut object FormatOutValidator { def toValidatedFormatOut( str: String ): Validated[NonEmptyList[String], FormatOut] = str match - case "pretty" => Validated.valid(FormatOut.Pretty) - case "raw" => Validated.valid(FormatOut.Raw) + case "pretty" => Validated.valid(FormatOut.Pretty) + case "raw" => Validated.valid(FormatOut.Raw) case other => Validated.invalidNel(s"Wrong format: $other") } diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYaml.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYaml.scala index 9e717cf..c869096 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYaml.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYaml.scala @@ -1,7 +1,6 @@ package ru.d10xa.jsonlogviewer.decline.yaml import ru.d10xa.jsonlogviewer.decline.Config -import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml import ru.d10xa.jsonlogviewer.query.QueryAST case class ConfigYaml( @@ -13,4 +12,3 @@ case class ConfigYaml( object ConfigYaml: val empty: ConfigYaml = ConfigYaml(None, None, None, None) - diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/Feed.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/Feed.scala index b61de99..292d1de 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/Feed.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/Feed.scala @@ -4,8 +4,9 @@ import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.query.QueryAST case class Feed( - name: String, + name: Option[String], commands: List[String], + inlineInput: Option[String], filter: Option[QueryAST], formatIn: Option[FormatIn] ) diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/ColorLineFormatter.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/ColorLineFormatter.scala index 0634ca0..efedaf6 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/ColorLineFormatter.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/ColorLineFormatter.scala @@ -7,7 +7,8 @@ import ru.d10xa.jsonlogviewer.OutputLineFormatter import ru.d10xa.jsonlogviewer.ParseResult import ru.d10xa.jsonlogviewer.decline.Config -class ColorLineFormatter(c: Config, feedName: Option[String]) extends OutputLineFormatter: +class ColorLineFormatter(c: Config, feedName: Option[String]) + extends OutputLineFormatter: private val strEmpty: Str = Str("") private val strSpace: Str = Str(" ") private val strNewLine: Str = Str("\n") diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/RawFormatter.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/RawFormatter.scala index 3ed4fe6..e21cef6 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/RawFormatter.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/formatout/RawFormatter.scala @@ -1,11 +1,8 @@ package ru.d10xa.jsonlogviewer.formatout -import fansi.ErrorMode.Strip -import fansi.EscapeAttr import fansi.Str import ru.d10xa.jsonlogviewer.OutputLineFormatter import ru.d10xa.jsonlogviewer.ParseResult -import ru.d10xa.jsonlogviewer.decline.Config class RawFormatter extends OutputLineFormatter: override def formatLine(p: ParseResult): Str = diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala index 928f3ec..5ba0a9d 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala @@ -1,7 +1,16 @@ package ru.d10xa.jsonlogviewer.shell +import cats.effect.IO import fs2.* -import cats.effect.* -trait Shell[F[_]] { - def mergeCommands(commands: List[String]): Stream[F, String] +trait Shell { + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): Stream[IO, String] } + +object Shell: + def stringToStream(str: String): fs2.Stream[IO, String] = + val strings = str.split("\n").filter(_.nonEmpty) + fs2.Stream.emits(strings) +end Shell diff --git a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/TimestampFilterTest.scala b/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/TimestampFilterTest.scala index e1fe7b2..47d9b7b 100644 --- a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/TimestampFilterTest.scala +++ b/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/TimestampFilterTest.scala @@ -1,24 +1,26 @@ package ru.d10xa.jsonlogviewer +import cats.effect.IO import fs2.Stream -import munit.FunSuite +import munit.CatsEffectSuite import java.time.ZonedDateTime -class TimestampFilterTest extends FunSuite { +class TimestampFilterTest extends CatsEffectSuite { test("filterTimestampAfter") { val filter = TimestampFilter() val t1 = pr("2023-09-17T19:10:01.132318Z") val t2 = pr("2023-09-19T19:10:03.132318Z") val stream = Stream.emits(Seq(t1, t2)) - val list = stream + stream .through( filter.filterTimestampAfter( Some(ZonedDateTime.parse("2023-09-18T19:10:02Z")) ) ) + .compile .toList - assertEquals(list, List(t2)) + .flatTap(list => IO(assertEquals(list, List(t2)))) } def pr(ts: String): ParseResult = ParseResult(