From c035ebfcf55d737c84f1d116ab4377df44b0124b Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 21 Dec 2024 20:14:29 +0300 Subject: [PATCH 01/15] refactoring, add munit to frontend, add inlineInput --- build.sbt | 14 +- .../src/main/scala/Ansi2HtmlWithClasses.scala | 2 +- frontend-laminar/src/main/scala/App.scala | 31 ++- .../src/main/scala/EditElement.scala | 2 +- frontend-laminar/src/main/scala/Router0.scala | 6 +- .../src/main/scala/ViewElement.scala | 100 +++++----- .../Ansi2HtmlWithClassesTest.scala | 46 +++++ .../decline/ConfigInitImpl.scala | 4 - .../decline/yaml/ConfigYamlLoaderImpl.scala | 8 + .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 3 - .../decline/ConfigInitImpl.scala | 6 +- .../decline/yaml/ConfigYamlLoaderImpl.scala | 188 ++++++++++++++++++ .../decline/yaml}/ConfigYamlReader.scala | 8 +- .../ru/d10xa/jsonlogviewer/Application.scala | 6 - .../d10xa/jsonlogviewer/ParseResultKeys.scala | 1 + .../d10xa/jsonlogviewer/decline/Config.scala | 3 - .../jsonlogviewer/decline/DeclineOpts.scala | 1 - .../decline/FormatInValidator.scala | 1 - .../decline/FormatOutValidator.scala | 2 - .../decline/yaml/ConfigYaml.scala | 1 - .../decline/yaml/ConfigYamlLoader.scala | 172 +--------------- .../jsonlogviewer/decline/yaml/Feed.scala | 1 + .../formatout/RawFormatter.scala | 3 - .../ru/d10xa/jsonlogviewer/shell/Shell.scala | 1 - .../decline/yaml/ConfigYamlLoaderTest.scala | 8 +- 25 files changed, 349 insertions(+), 269 deletions(-) create mode 100644 frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala create mode 100644 json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala create mode 100644 json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala rename json-log-viewer/{shared/src/main/scala/ru/d10xa/jsonlogviewer => jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml}/ConfigYamlReader.scala (74%) diff --git a/build.sbt b/build.sbt index 1263136..f92e057 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ 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 `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) .in(file("json-log-viewer")) @@ -36,20 +37,25 @@ 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, +// (Test / requireJsDomEnv) := true, +// (Test / scalaJSUseMainModuleInitializer) := false, +// (Test / scalaJSLinkerConfig) ~= { _.withModuleKind(ModuleKind.NoModule) } ) lazy val `make-logs` = project @@ -77,11 +83,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 diff --git a/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala b/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala index d0701e3..14141e7 100644 --- a/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala +++ b/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala @@ -6,7 +6,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) = 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/App.scala index f7dca2d..a30f648 100644 --- a/frontend-laminar/src/main/scala/App.scala +++ b/frontend-laminar/src/main/scala/App.scala @@ -1,20 +1,23 @@ import Router0.* +import Router0.EditPage +import Router0.HelpPage +import Router0.LivePage +import Router0.Page +import Router0.ViewPage +import Router0.navigateTo 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.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 +53,7 @@ object App { val formatInVar: Var[FormatIn] = Var( FormatIn.Json ) - val formatOutVar: Var[FormatOut] = Var( + val formatOutVar: Var[FormatOut] = Var( FormatOut.Pretty ) @@ -71,7 +74,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 +144,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 +167,7 @@ object App { onInput.mapToValue --> filterVar ) ) -) + ) def additionalArgsDiv: ReactiveHtmlElement[HTMLDivElement] = div( cls := "row-fluid", @@ -193,6 +202,7 @@ object App { } ) private def renderLivePage(): HtmlElement = { + implicit val owner: Owner = new Owner {} div( formatInDiv, formatOutDiv, @@ -205,6 +215,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/EditElement.scala index 46a703c..3dc1d02 100644 --- a/frontend-laminar/src/main/scala/EditElement.scala +++ b/frontend-laminar/src/main/scala/EditElement.scala @@ -1,5 +1,5 @@ -import com.raquo.laminar.api.L.{*, given} 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/Router0.scala index eb17fab..72c6360 100644 --- a/frontend-laminar/src/main/scala/Router0.scala +++ b/frontend-laminar/src/main/scala/Router0.scala @@ -1,8 +1,8 @@ -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.* -import org.scalajs.dom 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/ViewElement.scala b/frontend-laminar/src/main/scala/ViewElement.scala index 80fc995..8b3477e 100644 --- a/frontend-laminar/src/main/scala/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ViewElement.scala @@ -1,62 +1,68 @@ -import App.textVar +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.{*, 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 com.raquo.laminar.api.L.* 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 ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml +import ru.d10xa.jsonlogviewer.decline.yaml.Feed -import scala.util.chaining.scalaUtilChainingOps -object ViewElement { +import scala.concurrent.ExecutionContext.Implicits.global - 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" - ) +object ViewElement { - 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 = + )(implicit owner: Owner): HtmlElement = { + val eventBus = new EventBus[HtmlElement] + + logLinesSignal + .combineWith(configSignal) + .foreach { + case (string, Right(c)) => + c.copy(configYaml = + Some(c.configYaml.getOrElse( + ConfigYaml( + filter = None, + formatIn = None, + commands = None, + feeds = Some( + List( + Feed( + name = "inlineInput", + commands = List.empty, + inlineInput = Some(string), + filter = c.filter, + formatIn = c.formatIn + ) + ) + ) + ) + )) + ) + val stream: fs2.Stream[IO, HtmlElement] = LogViewerStream + .stream[IO](c) + .map(Ansi2HtmlWithClasses.apply) + .map(_.mkString("
", "", "
")) + .map(DomApi.unsafeParseHtmlString) + .map(foreignHtmlElement) + + stream.compile.lastOrError + .unsafeToFuture() + .foreach(eventBus.writer.onNext) + + case (_, Left(help)) => + eventBus.writer.onNext(pre(cls := "text-light", help.toString)) + }(owner) + pre( cls := "bg-dark font-monospace text-white", - child <-- runApp(logLinesSignal, configSignal) + 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..13316b8 --- /dev/null +++ b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala @@ -0,0 +1,46 @@ +package ru.d10xa.jsonlogviewer + +import fansi.Attr +import fansi.Bold +import fansi.Color +import fansi.Underlined +import munit.FunSuite + +class Ansi2HtmlWithClassesTest extends FunSuite { + + // Тест для функции transition + 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, "") + } + + // Тест для функции apply + 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/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/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala new file mode 100644 index 0000000..399250b --- /dev/null +++ b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -0,0 +1,8 @@ +package ru.d10xa.jsonlogviewer.decline.yaml + +import cats.data.ValidatedNel + +class ConfigYamlLoaderImpl extends ConfigYamlLoader { + + override def parseYamlFile(content: String): ValidatedNel[String, ConfigYaml] = ??? +} 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..ef882dc 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,10 +1,7 @@ package ru.d10xa.jsonlogviewer.shell -import cats.effect.* import fs2.* -import java.io.* - class ShellImpl[F[_]] extends Shell[F] { def mergeCommands(commands: List[String]): Stream[F, String] = Stream.empty 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..284c3ab 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,15 +2,17 @@ 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 class ConfigInitImpl extends ConfigInit { - + override def initConfig(c: Config): IO[Config] = { def findConfigFile( baseName: String, diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala new file mode 100644 index 0000000..39a6b0e --- /dev/null +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -0,0 +1,188 @@ +package ru.d10xa.jsonlogviewer.decline.yaml + +import cats.data.NonEmptyList +import cats.data.Validated +import cats.data.ValidatedNel +import cats.syntax.all.* +import io.circe.* +import io.circe.generic.auto.* +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.query.QueryAST + +class ConfigYamlLoaderImpl extends ConfigYamlLoader { + private def trimCommentedLines(str: String): String = + str.linesIterator + .filterNot(line => + line.trim.startsWith("#") || line.trim.startsWith("//") + ) + .mkString("\n") + .replace("\\n", " ") + .trim + + private def parseOptionalQueryAST( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[QueryAST]] = + parseOptionalStringField( + fields, + fieldName, + s"Invalid '$fieldName' field format" + ).andThen { + case Some(str) => + val trimmed = trimCommentedLines(str) + QueryASTValidator.toValidatedQueryAST(trimmed).map(Some(_)) + case None => Validated.valid(None) + } + + private def parseOptionalFormatIn( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[FormatIn]] = + parseOptionalStringField( + fields, + fieldName, + s"Invalid '$fieldName' field format" + ).andThen { + case Some(formatStr) => + FormatInValidator.toValidatedFormatIn(formatStr).map(Some(_)) + case None => Validated.valid(None) + } + + private def parseOptionalListString( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[List[String]]] = + fields.get(fieldName) match { + case Some(jsonValue) => + jsonValue + .as[List[String]] + .leftMap(_ => s"Invalid '$fieldName' field format") + .toValidatedNel + .map(Some(_)) + case None => Validated.valid(None) + } + + private def parseOptionalFeeds( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[List[Feed]]] = + fields.get(fieldName) match { + case Some(jsonValue) => + jsonValue + .as[List[Json]] + .leftMap(_ => s"Invalid '$fieldName' field format, should be a list") + .toValidatedNel + .andThen(_.traverse(parseFeed)) + .map(Some(_)) + case None => Validated.valid(None) + } + + private def parseOptionalStringField( + fields: Map[String, Json], + fieldName: String, + errorMsg: String + ): ValidatedNel[String, Option[String]] = + fields.get(fieldName) match { + case Some(jsonValue) => + jsonValue.as[String].leftMap(_ => errorMsg).toValidatedNel.map(Some(_)) + case None => Validated.valid(None) + } + + private def parseString( + fields: Map[String, Json], + fieldName: String, + errorMsg: String + ): ValidatedNel[String, String] = + fields.get(fieldName) match { + case Some(j) => + j.as[String].leftMap(_ => errorMsg).toValidatedNel + case None => + Validated.invalidNel(s"Missing '$fieldName' field in feed") + } + + private def parseListString( + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, List[String]] = + fields.get(fieldName) match { + case Some(c) => + c.as[List[String]] + .leftMap(_ => s"Invalid '$fieldName' field in feed") + .toValidatedNel + case None => + 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.invalidNel(s"Missing '$fieldName' field in feed") + } + + 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( + feedFields, + "name", + "Invalid 'name' field in feed" + ) + 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, inlineInputValidated, filterValidated, formatInValidated) + .mapN(Feed.apply) + } + + def parseYamlFile(content: String): ValidatedNel[String, ConfigYaml] = { + val uncommentedContent = content.linesIterator + .filterNot(line => line.trim.startsWith("#")) + .mkString("\n") + .trim + if (uncommentedContent.isEmpty) { + Validated.valid(ConfigYaml.empty) + } else { + parser.parse(content) match { + case Left(error) => + Validated.invalidNel(s"YAML parsing error: ${error.getMessage}") + case Right(json) => + json.asObject.map(_.toMap) match { + case None => Validated.invalidNel("YAML is not a valid JSON object") + case Some(fields) => + val filterValidated: ValidatedNel[String, Option[QueryAST]] = + parseOptionalQueryAST(fields, "filter") + + val formatInValidated: ValidatedNel[String, Option[FormatIn]] = + parseOptionalFormatIn(fields, "formatIn") + val commandsValidated + : ValidatedNel[String, Option[List[String]]] = + parseOptionalListString(fields, "commands") + val feedsValidated: ValidatedNel[String, Option[List[Feed]]] = + parseOptionalFeeds(fields, "feeds") + + ( + filterValidated, + formatInValidated, + commandsValidated, + feedsValidated + ).mapN(ConfigYaml.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/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/Application.scala index a104752..9e79aa9 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( 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/decline/Config.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/Config.scala index 3d10b44..4541389 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 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..885a5fa 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,8 +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 import ru.d10xa.jsonlogviewer.decline.Config.FormatOut object FormatOutValidator { 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..50c0b2e 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( diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala index 086817b..77b93c7 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala @@ -1,175 +1,7 @@ package ru.d10xa.jsonlogviewer.decline.yaml -import cats.data.NonEmptyList -import cats.data.Validated import cats.data.ValidatedNel -import cats.syntax.all.* -import io.circe.* -import io.circe.generic.auto.* -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 { - private def trimCommentedLines(str: String): String = - str.linesIterator - .filterNot(line => - line.trim.startsWith("#") || line.trim.startsWith("//") - ) - .mkString("\n") - .replace("\\n", " ") - .trim - - private def parseOptionalQueryAST( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[QueryAST]] = - parseOptionalStringField( - fields, - fieldName, - s"Invalid '$fieldName' field format" - ).andThen { - case Some(str) => - val trimmed = trimCommentedLines(str) - QueryASTValidator.toValidatedQueryAST(trimmed).map(Some(_)) - case None => Validated.valid(None) - } - - private def parseOptionalFormatIn( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[FormatIn]] = - parseOptionalStringField( - fields, - fieldName, - s"Invalid '$fieldName' field format" - ).andThen { - case Some(formatStr) => - FormatInValidator.toValidatedFormatIn(formatStr).map(Some(_)) - case None => Validated.valid(None) - } - - private def parseOptionalListString( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[List[String]]] = - fields.get(fieldName) match { - case Some(jsonValue) => - jsonValue - .as[List[String]] - .leftMap(_ => s"Invalid '$fieldName' field format") - .toValidatedNel - .map(Some(_)) - case None => Validated.valid(None) - } - - private def parseOptionalFeeds( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[List[Feed]]] = - fields.get(fieldName) match { - case Some(jsonValue) => - jsonValue - .as[List[Json]] - .leftMap(_ => s"Invalid '$fieldName' field format, should be a list") - .toValidatedNel - .andThen(_.traverse(parseFeed)) - .map(Some(_)) - case None => Validated.valid(None) - } - - private def parseOptionalStringField( - fields: Map[String, Json], - fieldName: String, - errorMsg: String - ): ValidatedNel[String, Option[String]] = - fields.get(fieldName) match { - case Some(jsonValue) => - jsonValue.as[String].leftMap(_ => errorMsg).toValidatedNel.map(Some(_)) - case None => Validated.valid(None) - } - - private def parseString( - fields: Map[String, Json], - fieldName: String, - errorMsg: String - ): ValidatedNel[String, String] = - fields.get(fieldName) match { - case Some(j) => - j.as[String].leftMap(_ => errorMsg).toValidatedNel - case None => - Validated.invalidNel(s"Missing '$fieldName' field in feed") - } - - private def parseListString( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, List[String]] = - fields.get(fieldName) match { - case Some(c) => - c.as[List[String]] - .leftMap(_ => s"Invalid '$fieldName' field in feed") - .toValidatedNel - case None => - Validated.invalidNel(s"Missing '$fieldName' field in feed") - } - - 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( - feedFields, - "name", - "Invalid 'name' field in feed" - ) - val commandsValidated = parseListString(feedFields, "commands") - val filterValidated = parseOptionalQueryAST(feedFields, "filter") - val formatInValidated - : Validated[NonEmptyList[String], Option[FormatIn]] = - parseOptionalFormatIn(feedFields, "formatIn") - - (nameValidated, commandsValidated, filterValidated, formatInValidated) - .mapN(Feed.apply) - } - - def parseYamlFile(content: String): ValidatedNel[String, ConfigYaml] = { - val uncommentedContent = content.linesIterator - .filterNot(line => line.trim.startsWith("#")) - .mkString("\n") - .trim - if (uncommentedContent.isEmpty) { - Validated.valid(ConfigYaml.empty) - } else { - parser.parse(content) match { - case Left(error) => - Validated.invalidNel(s"YAML parsing error: ${error.getMessage}") - case Right(json) => - json.asObject.map(_.toMap) match { - case None => Validated.invalidNel("YAML is not a valid JSON object") - case Some(fields) => - val filterValidated: ValidatedNel[String, Option[QueryAST]] = - parseOptionalQueryAST(fields, "filter") - - val formatInValidated: ValidatedNel[String, Option[FormatIn]] = - parseOptionalFormatIn(fields, "formatIn") - val commandsValidated - : ValidatedNel[String, Option[List[String]]] = - parseOptionalListString(fields, "commands") - val feedsValidated: ValidatedNel[String, Option[List[Feed]]] = - parseOptionalFeeds(fields, "feeds") - - ( - filterValidated, - formatInValidated, - commandsValidated, - feedsValidated - ).mapN(ConfigYaml.apply) - } - } - } - } +trait ConfigYamlLoader { + def parseYamlFile(content: String): ValidatedNel[String, ConfigYaml] } 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..1da011c 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 @@ -6,6 +6,7 @@ import ru.d10xa.jsonlogviewer.query.QueryAST case class Feed( name: 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/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..5ba8564 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,6 +1,5 @@ package ru.d10xa.jsonlogviewer.shell import fs2.* -import cats.effect.* trait Shell[F[_]] { def mergeCommands(commands: List[String]): Stream[F, String] diff --git a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala b/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala index 3078b61..bd85dbf 100644 --- a/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala +++ b/json-log-viewer/shared/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala @@ -6,6 +6,8 @@ import ru.d10xa.jsonlogviewer.decline.Config.FormatIn import ru.d10xa.jsonlogviewer.query.QueryAST class ConfigYamlLoaderTest extends FunSuite { + + private val configYamlLoader: ConfigYamlLoader = new ConfigYamlLoaderImpl test("parse valid yaml with feeds") { val yaml = @@ -31,7 +33,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 @@ -61,7 +63,7 @@ class ConfigYamlLoaderTest extends FunSuite { 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 +78,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 From 2c399f8d1e79da2cb079a4a3d5b37b2a2001409c Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 21 Dec 2024 22:16:07 +0300 Subject: [PATCH 02/15] refactoring, add munit to frontend, add inlineInput --- .../src/main/scala/ViewElement.scala | 2 +- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 5 ++-- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 16 +++++----- .../ru/d10xa/jsonlogviewer/Application.scala | 2 +- .../d10xa/jsonlogviewer/LogViewerStream.scala | 30 +++++++++---------- .../d10xa/jsonlogviewer/TimestampFilter.scala | 13 ++++---- .../ru/d10xa/jsonlogviewer/shell/Shell.scala | 5 ++-- .../jsonlogviewer/TimestampFilterTest.scala | 3 +- 8 files changed, 39 insertions(+), 37 deletions(-) diff --git a/frontend-laminar/src/main/scala/ViewElement.scala b/frontend-laminar/src/main/scala/ViewElement.scala index 8b3477e..1a5d18c 100644 --- a/frontend-laminar/src/main/scala/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ViewElement.scala @@ -46,7 +46,7 @@ object ViewElement { )) ) val stream: fs2.Stream[IO, HtmlElement] = LogViewerStream - .stream[IO](c) + .stream(c) .map(Ansi2HtmlWithClasses.apply) .map(_.mkString("
", "", "
")) .map(DomApi.unsafeParseHtmlString) 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 ef882dc..2614927 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,9 +1,10 @@ package ru.d10xa.jsonlogviewer.shell +import cats.effect.IO import fs2.* -class ShellImpl[F[_]] extends Shell[F] { +class ShellImpl extends Shell { - def mergeCommands(commands: List[String]): Stream[F, String] = Stream.empty + def mergeCommands(commands: List[String]): Stream[IO, String] = Stream.empty } 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..c6f03f6 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,31 +3,31 @@ 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] = + 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] = { + def mergeCommands(commands: List[String]): fs2.Stream[IO, String] = { val streams = commands.map(runInfiniteCommand) fs2.Stream.emits(streams).parJoin(math.max(1, commands.length)) } 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 9e79aa9..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 @@ -20,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..8aabbbd 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,6 +1,6 @@ package ru.d10xa.jsonlogviewer -import cats.effect.Async +import cats.effect.IO import cats.syntax.all.* import fs2.* import fs2.io.* @@ -30,24 +30,24 @@ object LogViewerStream { } } - private def commandsToStream[F[_]: Async]( + private def commandsToStream( commands: List[String] - ): Stream[F, String] = { - new ShellImpl[F]().mergeCommands(commands) + ): Stream[IO, String] = { + new ShellImpl().mergeCommands(commands) } - private def stdinLinesStream[F[_]: Async]: Stream[F, String] = - stdinUtf8[F](1024 * 1024 * 10) + private def stdinLinesStream: Stream[IO, String] = + stdinUtf8[IO](1024 * 1024 * 10) .repartition(s => Chunk.array(s.split("\n", -1))) .filter(_.nonEmpty) - 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( @@ -69,10 +69,10 @@ object LogViewerStream { .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,7 +80,7 @@ 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]] = @@ -89,7 +89,7 @@ object LogViewerStream { val finalStream = feedsOpt match { case Some(feeds) => val feedStreams = feeds.map { feed => - val feedStream = commandsToStream[F](feed.commands) + val feedStream = commandsToStream(feed.commands) processStream( config, feedStream, @@ -102,8 +102,8 @@ object LogViewerStream { case None => val baseStream = topCommandsOpt match { - case Some(cmds) => commandsToStream[F](cmds) - case None => stdinLinesStream[F] + case Some(cmds) => commandsToStream(cmds) + case None => stdinLinesStream } processStream(config, baseStream, None, None, None) } 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/shell/Shell.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala index 5ba8564..3fa6dbe 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,6 +1,7 @@ package ru.d10xa.jsonlogviewer.shell +import cats.effect.IO import fs2.* -trait Shell[F[_]] { - def mergeCommands(commands: List[String]): Stream[F, String] +trait Shell { + def mergeCommands(commands: List[String]): Stream[IO, String] } 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..1443405 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 @@ -16,8 +16,7 @@ class TimestampFilterTest extends FunSuite { filter.filterTimestampAfter( Some(ZonedDateTime.parse("2023-09-18T19:10:02Z")) ) - ) - .toList + ).compile.toList assertEquals(list, List(t2)) } From f8108be44fe6123e7aa20f3dec4fd73754d7b6f5 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 24 Dec 2024 19:53:03 +0300 Subject: [PATCH 03/15] upd --- build.sbt | 13 +++++++++++-- .../src/main/resources/index-fastopt.html | 2 +- .../src/main/resources/index-fullopt.html | 2 +- .../d10xa/jsonlogviewer/TimestampFilterTest.scala | 12 +++++++++--- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index f92e057..0a8e495 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" @@ -19,6 +19,13 @@ 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")) .jvmEnablePlugins(JavaAppPackaging) @@ -53,6 +60,7 @@ lazy val `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) .jsSettings( publish / skip := true, fork := false, +// scalaJSLinkerConfig ~= { _.withOptimizer(false) } // (Test / requireJsDomEnv) := true, // (Test / scalaJSUseMainModuleInitializer) := false, // (Test / scalaJSLinkerConfig) ~= { _.withModuleKind(ModuleKind.NoModule) } @@ -92,5 +100,6 @@ lazy val `frontend-laminar` = project scalaJSUseMainModuleInitializer := true, (Test / requireJsDomEnv) := true, - useYarn := true +// useYarn := true, +// scalaJSLinkerConfig ~= { _.withOptimizer(false) } ) 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/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 1443405..102d719 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,9 +1,14 @@ package ru.d10xa.jsonlogviewer +import cats.effect.IO import fs2.Stream +import fs2.io.* +import cats.effect.syntax.all.* import munit.FunSuite - +import cats.effect.unsafe.implicits.* +import scala.concurrent.duration.* import java.time.ZonedDateTime +import scala.concurrent.Await class TimestampFilterTest extends FunSuite { test("filterTimestampAfter") { @@ -16,8 +21,9 @@ class TimestampFilterTest extends FunSuite { filter.filterTimestampAfter( Some(ZonedDateTime.parse("2023-09-18T19:10:02Z")) ) - ).compile.toList - assertEquals(list, List(t2)) + ).compile.toList.unsafeToFuture() + + assertEquals( Await.result(list, 1.second), List(t2)) } def pr(ts: String): ParseResult = ParseResult( From 40f267da9851bd069758d9b0f206dc6660deef74 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 24 Dec 2024 20:41:07 +0300 Subject: [PATCH 04/15] Move stdin outside shared folder --- .../d10xa/jsonlogviewer/StdInLinesStreamImpl.scala | 7 +++++++ .../d10xa/jsonlogviewer/StdInLinesStreamImpl.scala | 13 +++++++++++++ .../ru/d10xa/jsonlogviewer/LogViewerStream.scala | 7 +++---- .../ru/d10xa/jsonlogviewer/StdInLinesStream.scala | 6 ++++++ 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala create mode 100644 json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStreamImpl.scala create mode 100644 json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/StdInLinesStream.scala 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/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/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala index 8aabbbd..961b9a9 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 @@ -4,6 +4,7 @@ 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 @@ -36,10 +37,8 @@ object LogViewerStream { new ShellImpl().mergeCommands(commands) } - private def stdinLinesStream: Stream[IO, String] = - stdinUtf8[IO](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( baseConfig: Config, 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] +} From 45f571fd2ad69880a435e0970f007a384f57a6b3 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Fri, 3 Jan 2025 16:14:34 +0300 Subject: [PATCH 05/15] debug --- .../jsonlogviewer}/Ansi2HtmlWithClasses.scala | 2 + .../{ => ru/d10xa/jsonlogviewer}/App.scala | 26 +++++++++---- .../d10xa/jsonlogviewer}/EditElement.scala | 2 + .../d10xa/jsonlogviewer}/Router0.scala | 2 + .../d10xa/jsonlogviewer}/ViewElement.scala | 37 +++++++++++-------- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 9 ++++- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 17 ++++++--- .../jsonlogviewer/JsonLogLineParser.scala | 1 + .../d10xa/jsonlogviewer/LogViewerStream.scala | 27 ++++++++++---- .../ru/d10xa/jsonlogviewer/shell/Shell.scala | 2 +- 10 files changed, 88 insertions(+), 37 deletions(-) rename frontend-laminar/src/main/scala/{ => ru/d10xa/jsonlogviewer}/Ansi2HtmlWithClasses.scala (98%) rename frontend-laminar/src/main/scala/{ => ru/d10xa/jsonlogviewer}/App.scala (90%) rename frontend-laminar/src/main/scala/{ => ru/d10xa/jsonlogviewer}/EditElement.scala (89%) rename frontend-laminar/src/main/scala/{ => ru/d10xa/jsonlogviewer}/Router0.scala (98%) rename frontend-laminar/src/main/scala/{ => ru/d10xa/jsonlogviewer}/ViewElement.scala (69%) diff --git a/frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala similarity index 98% rename from frontend-laminar/src/main/scala/Ansi2HtmlWithClasses.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala index 14141e7..86fa0e6 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 diff --git a/frontend-laminar/src/main/scala/App.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala similarity index 90% rename from frontend-laminar/src/main/scala/App.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala index a30f648..4eac031 100644 --- a/frontend-laminar/src/main/scala/App.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala @@ -1,10 +1,5 @@ -import Router0.* -import Router0.EditPage -import Router0.HelpPage -import Router0.LivePage -import Router0.Page -import Router0.ViewPage -import Router0.navigateTo +package ru.d10xa.jsonlogviewer + import com.monovore.decline.Help import com.raquo.laminar.api.L import com.raquo.laminar.api.L.* @@ -13,6 +8,23 @@ import com.raquo.waypoint.* 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.EditPage +import ru.d10xa.jsonlogviewer.Router0.EditPage +import ru.d10xa.jsonlogviewer.Router0.HelpPage +import ru.d10xa.jsonlogviewer.Router0.HelpPage +import ru.d10xa.jsonlogviewer.Router0.HelpPage +import ru.d10xa.jsonlogviewer.Router0.LivePage +import ru.d10xa.jsonlogviewer.Router0.LivePage +import ru.d10xa.jsonlogviewer.Router0.LivePage +import ru.d10xa.jsonlogviewer.Router0.Page +import ru.d10xa.jsonlogviewer.Router0.Page +import ru.d10xa.jsonlogviewer.Router0.ViewPage +import ru.d10xa.jsonlogviewer.Router0.ViewPage +import ru.d10xa.jsonlogviewer.Router0.ViewPage +import ru.d10xa.jsonlogviewer.Router0.navigateTo +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.FormatIn.Json diff --git a/frontend-laminar/src/main/scala/EditElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala similarity index 89% rename from frontend-laminar/src/main/scala/EditElement.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala index 3dc1d02..ae9f6df 100644 --- a/frontend-laminar/src/main/scala/EditElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/EditElement.scala @@ -1,3 +1,5 @@ +package ru.d10xa.jsonlogviewer + import com.raquo.laminar.api.L import com.raquo.laminar.api.L.* object EditElement { diff --git a/frontend-laminar/src/main/scala/Router0.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala similarity index 98% rename from frontend-laminar/src/main/scala/Router0.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala index 72c6360..96155dd 100644 --- a/frontend-laminar/src/main/scala/Router0.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Router0.scala @@ -1,3 +1,5 @@ +package ru.d10xa.jsonlogviewer + import com.github.plokhotnyuk.jsoniter_scala.core.* import com.github.plokhotnyuk.jsoniter_scala.macros.* import com.raquo.laminar.api.L.* diff --git a/frontend-laminar/src/main/scala/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala similarity index 69% rename from frontend-laminar/src/main/scala/ViewElement.scala rename to frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala index 1a5d18c..adac934 100644 --- a/frontend-laminar/src/main/scala/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -1,3 +1,5 @@ +package ru.d10xa.jsonlogviewer + import cats.effect.IO import cats.effect.unsafe.implicits.global import com.monovore.decline.Help @@ -25,28 +27,31 @@ object ViewElement { .combineWith(configSignal) .foreach { case (string, Right(c)) => - c.copy(configYaml = - Some(c.configYaml.getOrElse( - ConfigYaml( - filter = None, - formatIn = None, - commands = None, - feeds = Some( - List( - Feed( - name = "inlineInput", - commands = List.empty, - inlineInput = Some(string), - filter = c.filter, - formatIn = c.formatIn + println(s"string = ${string}") + val c2 = c.copy(configYaml = + Some( + c.configYaml.getOrElse( + ConfigYaml( + filter = None, + formatIn = None, + commands = None, + feeds = Some( + List( + Feed( + name = "inlineInput", + commands = List.empty, + inlineInput = Some(string), + filter = c.filter, + formatIn = c.formatIn + ) ) ) ) ) - )) + ) ) val stream: fs2.Stream[IO, HtmlElement] = LogViewerStream - .stream(c) + .stream(c2) .map(Ansi2HtmlWithClasses.apply) .map(_.mkString("
", "", "
")) .map(DomApi.unsafeParseHtmlString) 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 2614927..1ac63cb 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 @@ -5,6 +5,13 @@ import fs2.* class ShellImpl extends Shell { - def mergeCommands(commands: List[String]): Stream[IO, String] = Stream.empty + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): Stream[IO, String] = inlineInput match + case Some(inlineInput) => + println(s"mergeCommandsAndInlineInput.inlineInput = ${inlineInput.length}") + Stream.eval(IO(inlineInput.trim)) + case None => Stream.empty } 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 c6f03f6..0c0a4a0 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 @@ -12,7 +12,7 @@ class ShellImpl extends Shell { .start() })(process => IO(process.destroy())) - def runInfiniteCommand(command: String): fs2.Stream[IO, String] = + private def runInfiniteCommand(command: String): fs2.Stream[IO, String] = fs2.Stream.resource(createProcess(command)).flatMap { process => fs2.io .readInputStream( @@ -22,14 +22,21 @@ class ShellImpl extends Shell { ) .through(fs2.text.utf8.decode) .through(fs2.text.lines) - .onFinalize(IO{ + .onFinalize(IO { process.waitFor() }.void) } - def mergeCommands(commands: List[String]): fs2.Stream[IO, String] = { - val streams = commands.map(runInfiniteCommand) + private def stringToStream(str: String): fs2.Stream[IO, String] = + fs2.Stream.eval(IO.pure(str)) + + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): fs2.Stream[IO, String] = { + val streams = + commands.map(runInfiniteCommand) ++ inlineInput.map(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/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala index d8fd8d0..37005ec 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala @@ -57,6 +57,7 @@ class JsonLogLineParser(config: Config, jsonPrefixPostfix: JsonPrefixPostfix) otherAttributes = attributes ) override def parse(s: String): ParseResult = + println(s"""parse ${s.replace("\n", " ")}""") val (middle, prefixOpt, postfixOpt) = jsonPrefixPostfix.detectJson(s) decode[ParsedLine](middle).toOption .map(pl => 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 961b9a9..f265505 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 @@ -31,10 +31,11 @@ object LogViewerStream { } } - private def commandsToStream( - commands: List[String] + private def commandsAndInlineInputToStream( + commands: List[String], + inlineInput: Option[String] ): Stream[IO, String] = { - new ShellImpl().mergeCommands(commands) + new ShellImpl().mergeCommandsAndInlineInput(commands, inlineInput) } private val stdinLinesStream: Stream[IO, String] = @@ -64,7 +65,10 @@ object LogViewerStream { ColorLineFormatter(effectiveConfig, feedName) lines - .map(logLineParser.parse) + .map { it => + println(s"""parse result $it""") + logLineParser.parse(it) + } .filter(logLineFilter.grep) .filter(logLineFilter.logLineQueryPredicate) .through( @@ -80,15 +84,19 @@ object LogViewerStream { } def stream(config: Config): Stream[IO, String] = { + println("config: " + config) 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(feed.commands) + println(s"""feed = ${feed.toString.replace("\n", " ")}""") + val feedStream: Stream[IO, String] = + commandsAndInlineInputToStream(feed.commands, feed.inlineInput) processStream( config, feedStream, @@ -97,12 +105,17 @@ object LogViewerStream { feed.name.some ) } + println("parjoin") Stream.emits(feedStreams).parJoin(feedStreams.size) case None => val baseStream = topCommandsOpt match { - case Some(cmds) => commandsToStream(cmds) - case None => stdinLinesStream + case Some(cmds) => + println("commands") + commandsAndInlineInputToStream(cmds, None) + case None => + println("none") + stdinLinesStream } processStream(config, baseStream, None, None, None) } 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 3fa6dbe..0d1cf54 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 @@ -3,5 +3,5 @@ import cats.effect.IO import fs2.* trait Shell { - def mergeCommands(commands: List[String]): Stream[IO, String] + def mergeCommandsAndInlineInput(commands: List[String], inlineInput: Option[String]): Stream[IO, String] } From 4cc2cf57c670cf1e14f0acf78f99222f8e854ad5 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 4 Jan 2025 21:04:54 +0300 Subject: [PATCH 06/15] Remove debug. Fix frontend output --- .../jsonlogviewer/Ansi2HtmlWithClasses.scala | 2 +- .../ru/d10xa/jsonlogviewer/ViewElement.scala | 23 ++++++++----------- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 3 +-- .../decline/yaml/ConfigYamlLoaderImpl.scala | 2 +- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 5 +--- .../jsonlogviewer/JsonLogLineParser.scala | 1 - .../d10xa/jsonlogviewer/LogViewerStream.scala | 6 ----- .../ru/d10xa/jsonlogviewer/shell/Shell.scala | 6 +++++ 8 files changed, 20 insertions(+), 28 deletions(-) diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala index 86fa0e6..3b71760 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClasses.scala @@ -8,7 +8,7 @@ import scala.scalajs.js.annotation.JSExport /** https://github.com/com-lihaoyi/fansi/issues/62 */ object Ansi2HtmlWithClasses extends Function1[String, String]: - 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/ru/d10xa/jsonlogviewer/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala index adac934..476e9a1 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -8,12 +8,10 @@ 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.LogViewerStream import ru.d10xa.jsonlogviewer.decline.Config import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYaml import ru.d10xa.jsonlogviewer.decline.yaml.Feed -import scala.concurrent.ExecutionContext.Implicits.global object ViewElement { @@ -22,12 +20,10 @@ object ViewElement { configSignal: Signal[Either[Help, Config]] )(implicit owner: Owner): HtmlElement = { val eventBus = new EventBus[HtmlElement] - logLinesSignal .combineWith(configSignal) .foreach { case (string, Right(c)) => - println(s"string = ${string}") val c2 = c.copy(configYaml = Some( c.configYaml.getOrElse( @@ -50,16 +46,17 @@ object ViewElement { ) ) ) - val stream: fs2.Stream[IO, HtmlElement] = LogViewerStream + LogViewerStream .stream(c2) - .map(Ansi2HtmlWithClasses.apply) - .map(_.mkString("
", "", "
")) - .map(DomApi.unsafeParseHtmlString) - .map(foreignHtmlElement) - - stream.compile.lastOrError - .unsafeToFuture() - .foreach(eventBus.writer.onNext) + .compile + .toList + .flatMap { strings => + val base = foreignHtmlElement(DomApi.unsafeParseHtmlString(strings + .map(Ansi2HtmlWithClasses.apply) + .mkString("
", "", "
"))) + IO(eventBus.writer.onNext(base)) + } + .unsafeRunAndForget() case (_, Left(help)) => eventBus.writer.onNext(pre(cls := "text-light", help.toString)) 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 1ac63cb..b3e74e6 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 @@ -10,8 +10,7 @@ class ShellImpl extends Shell { inlineInput: Option[String] ): Stream[IO, String] = inlineInput match case Some(inlineInput) => - println(s"mergeCommandsAndInlineInput.inlineInput = ${inlineInput.length}") - Stream.eval(IO(inlineInput.trim)) + Shell.stringToStream(inlineInput) case None => Stream.empty } diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala index 39a6b0e..1c7f630 100644 --- a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -126,7 +126,7 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { .leftMap(_ => s"Invalid '$fieldName' field in feed") .toValidatedNel case None => - Validated.invalidNel(s"Missing '$fieldName' field in feed") + Validated.valid(None) } private def parseFeed(feedJson: Json): ValidatedNel[String, Feed] = 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 0c0a4a0..31f20cc 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 @@ -27,15 +27,12 @@ class ShellImpl extends Shell { }.void) } - private def stringToStream(str: String): fs2.Stream[IO, String] = - fs2.Stream.eval(IO.pure(str)) - def mergeCommandsAndInlineInput( commands: List[String], inlineInput: Option[String] ): fs2.Stream[IO, String] = { val streams = - commands.map(runInfiniteCommand) ++ inlineInput.map(stringToStream).toList + commands.map(runInfiniteCommand) ++ inlineInput.map(Shell.stringToStream).toList fs2.Stream.emits(streams).parJoin(math.max(1, commands.length)) } diff --git a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala index 37005ec..d8fd8d0 100644 --- a/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala +++ b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/JsonLogLineParser.scala @@ -57,7 +57,6 @@ class JsonLogLineParser(config: Config, jsonPrefixPostfix: JsonPrefixPostfix) otherAttributes = attributes ) override def parse(s: String): ParseResult = - println(s"""parse ${s.replace("\n", " ")}""") val (middle, prefixOpt, postfixOpt) = jsonPrefixPostfix.detectJson(s) decode[ParsedLine](middle).toOption .map(pl => 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 f265505..cf4edd2 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 @@ -66,7 +66,6 @@ object LogViewerStream { lines .map { it => - println(s"""parse result $it""") logLineParser.parse(it) } .filter(logLineFilter.grep) @@ -84,7 +83,6 @@ object LogViewerStream { } def stream(config: Config): Stream[IO, String] = { - println("config: " + config) val topCommandsOpt: Option[List[String]] = config.configYaml.flatMap(_.commands).filter(_.nonEmpty) @@ -94,7 +92,6 @@ object LogViewerStream { val finalStream = feedsOpt match { case Some(feeds) => val feedStreams = feeds.map { feed => - println(s"""feed = ${feed.toString.replace("\n", " ")}""") val feedStream: Stream[IO, String] = commandsAndInlineInputToStream(feed.commands, feed.inlineInput) processStream( @@ -105,16 +102,13 @@ object LogViewerStream { feed.name.some ) } - println("parjoin") Stream.emits(feedStreams).parJoin(feedStreams.size) case None => val baseStream = topCommandsOpt match { case Some(cmds) => - println("commands") commandsAndInlineInputToStream(cmds, None) case None => - println("none") stdinLinesStream } processStream(config, baseStream, None, None, None) 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 0d1cf54..f09f3a2 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 @@ -5,3 +5,9 @@ import fs2.* 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 From 8b80c993496df969138077342b16fea9da379517 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 4 Jan 2025 21:11:26 +0300 Subject: [PATCH 07/15] optional feed name --- .../src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala | 2 +- .../jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala | 5 ++--- .../main/scala/ru/d10xa/jsonlogviewer/LogViewerStream.scala | 2 +- .../scala/ru/d10xa/jsonlogviewer/decline/yaml/Feed.scala | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala index 476e9a1..c2b54fd 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -34,7 +34,7 @@ object ViewElement { feeds = Some( List( Feed( - name = "inlineInput", + name = None, commands = List.empty, inlineInput = Some(string), filter = c.filter, diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala index 1c7f630..9d96511 100644 --- a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -133,10 +133,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { 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") 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 cf4edd2..edbe328 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 @@ -99,7 +99,7 @@ object LogViewerStream { feedStream, feed.filter, feed.formatIn, - feed.name.some + feed.name ) } Stream.emits(feedStreams).parJoin(feedStreams.size) 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 1da011c..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,7 +4,7 @@ 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], From 635efbe90accb9c0f5f8a1630b33d7c8a328405a Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 4 Jan 2025 21:13:39 +0300 Subject: [PATCH 08/15] remove duplicate imports --- .../src/main/scala/ru/d10xa/jsonlogviewer/App.scala | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala index 4eac031..a46886d 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/App.scala @@ -10,20 +10,10 @@ 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.EditPage -import ru.d10xa.jsonlogviewer.Router0.EditPage -import ru.d10xa.jsonlogviewer.Router0.HelpPage import ru.d10xa.jsonlogviewer.Router0.HelpPage -import ru.d10xa.jsonlogviewer.Router0.HelpPage -import ru.d10xa.jsonlogviewer.Router0.LivePage import ru.d10xa.jsonlogviewer.Router0.LivePage -import ru.d10xa.jsonlogviewer.Router0.LivePage -import ru.d10xa.jsonlogviewer.Router0.Page import ru.d10xa.jsonlogviewer.Router0.Page import ru.d10xa.jsonlogviewer.Router0.ViewPage -import ru.d10xa.jsonlogviewer.Router0.ViewPage -import ru.d10xa.jsonlogviewer.Router0.ViewPage -import ru.d10xa.jsonlogviewer.Router0.navigateTo import ru.d10xa.jsonlogviewer.Router0.navigateTo import ru.d10xa.jsonlogviewer.decline.Config import ru.d10xa.jsonlogviewer.decline.Config.FormatIn From 2de4fa6f22c37c4f0c74a05dc1d9ed659fec9252 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 4 Jan 2025 21:20:04 +0300 Subject: [PATCH 09/15] remove ConfigYamlLoader from js --- build.sbt | 10 ++-------- .../decline/yaml/ConfigYamlLoaderImpl.scala | 8 -------- .../jsonlogviewer/decline/yaml/ConfigYamlLoader.scala | 0 3 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala rename json-log-viewer/{shared => jvm}/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoader.scala (100%) diff --git a/build.sbt b/build.sbt index 0a8e495..e8fa93f 100644 --- a/build.sbt +++ b/build.sbt @@ -59,11 +59,7 @@ lazy val `json-log-viewer` = crossProject(JSPlatform, JVMPlatform) ) .jsSettings( publish / skip := true, - fork := false, -// scalaJSLinkerConfig ~= { _.withOptimizer(false) } -// (Test / requireJsDomEnv) := true, -// (Test / scalaJSUseMainModuleInitializer) := false, -// (Test / scalaJSLinkerConfig) ~= { _.withModuleKind(ModuleKind.NoModule) } + fork := false ) lazy val `make-logs` = project @@ -99,7 +95,5 @@ lazy val `frontend-laminar` = project scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, - (Test / requireJsDomEnv) := true, -// useYarn := true, -// scalaJSLinkerConfig ~= { _.withOptimizer(false) } + (Test / requireJsDomEnv) := true ) diff --git a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala deleted file mode 100644 index 399250b..0000000 --- a/json-log-viewer/js/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala +++ /dev/null @@ -1,8 +0,0 @@ -package ru.d10xa.jsonlogviewer.decline.yaml - -import cats.data.ValidatedNel - -class ConfigYamlLoaderImpl extends ConfigYamlLoader { - - override 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/ConfigYamlLoader.scala similarity index 100% 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/ConfigYamlLoader.scala From c16bf7fab6ebfaedf860ac466bbc6056fbb60845 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Sat, 4 Jan 2025 21:57:14 +0300 Subject: [PATCH 10/15] refactoring ViewElement --- .../ru/d10xa/jsonlogviewer/ViewElement.scala | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala index c2b54fd..f47145a 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -12,9 +12,39 @@ 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]] @@ -24,38 +54,12 @@ object ViewElement { .combineWith(configSignal) .foreach { case (string, Right(c)) => - val c2 = c.copy(configYaml = - Some( - c.configYaml.getOrElse( - ConfigYaml( - filter = None, - formatIn = None, - commands = None, - feeds = Some( - List( - Feed( - name = None, - commands = List.empty, - inlineInput = Some(string), - filter = c.filter, - formatIn = c.formatIn - ) - ) - ) - ) - ) - ) - ) LogViewerStream - .stream(c2) + .stream(modifyConfigForInlineInput(string, c)) .compile .toList - .flatMap { strings => - val base = foreignHtmlElement(DomApi.unsafeParseHtmlString(strings - .map(Ansi2HtmlWithClasses.apply) - .mkString("
", "", "
"))) - IO(eventBus.writer.onNext(base)) - } + .map(stringsToHtmlElement) + .flatMap(e => IO(eventBus.writer.onNext(e))) .unsafeRunAndForget() case (_, Left(help)) => From 66af3e4cbe289acc895e86fa1ee5f8e1bb5e7e99 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 7 Jan 2025 20:48:35 +0300 Subject: [PATCH 11/15] fix tests --- .../ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala | 1 - .../jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename json-log-viewer/{shared => jvm}/src/test/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderTest.scala (91%) diff --git a/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala index 13316b8..9065050 100644 --- a/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala +++ b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala @@ -30,7 +30,6 @@ class Ansi2HtmlWithClassesTest extends FunSuite { assertEquals(result, "") } - // Тест для функции apply test("apply correctly handles a simple ANSI string") { val input = "\u001b[1mBold\u001b[0m" val result = Ansi2HtmlWithClasses.apply(input) 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 91% 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 bd85dbf..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,12 @@ 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") { @@ -48,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") @@ -56,7 +58,7 @@ 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)) } From 9f3f4b56069512f99d4251119bbe3dad7192ab4d Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 7 Jan 2025 20:49:22 +0300 Subject: [PATCH 12/15] fix tests --- .../scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala index 9065050..f32e792 100644 --- a/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala +++ b/frontend-laminar/src/test/scala/ru/d10xa/jsonlogviewer/Ansi2HtmlWithClassesTest.scala @@ -8,7 +8,6 @@ import munit.FunSuite class Ansi2HtmlWithClassesTest extends FunSuite { - // Тест для функции transition test("transition correctly handles Bold.On to Bold.Off") { val from = Bold.On val to = Bold.Off From 20aa87bb1444c1911c0fafb3f940a25939e58422 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 7 Jan 2025 20:51:14 +0300 Subject: [PATCH 13/15] scalafmt --- .../ru/d10xa/jsonlogviewer/ViewElement.scala | 2 +- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 2 +- .../decline/ConfigInitImpl.scala | 2 +- .../decline/yaml/ConfigYamlLoaderImpl.scala | 67 ++++++++++--------- .../d10xa/jsonlogviewer/shell/ShellImpl.scala | 3 +- .../d10xa/jsonlogviewer/LogViewerStream.scala | 2 +- .../d10xa/jsonlogviewer/decline/Config.scala | 4 +- .../decline/FormatOutValidator.scala | 4 +- .../decline/yaml/ConfigYaml.scala | 1 - .../formatout/ColorLineFormatter.scala | 3 +- .../ru/d10xa/jsonlogviewer/shell/Shell.scala | 5 +- 11 files changed, 53 insertions(+), 42 deletions(-) diff --git a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala index f47145a..2a3a8cc 100644 --- a/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala +++ b/frontend-laminar/src/main/scala/ru/d10xa/jsonlogviewer/ViewElement.scala @@ -44,7 +44,7 @@ object ViewElement { ) ) ) - + def render( logLinesSignal: Signal[String], configSignal: Signal[Either[Help, Config]] 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 b3e74e6..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 @@ -11,6 +11,6 @@ class ShellImpl extends Shell { ): Stream[IO, String] = inlineInput match case Some(inlineInput) => Shell.stringToStream(inlineInput) - case None => Stream.empty + case None => Stream.empty } 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 284c3ab..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 @@ -12,7 +12,7 @@ import ru.d10xa.jsonlogviewer.decline.yaml.ConfigYamlReader import java.io.File class ConfigInitImpl extends ConfigInit { - + override def initConfig(c: Config): IO[Config] = { def findConfigFile( baseName: String, diff --git a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala index 9d96511..bf08d35 100644 --- a/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala +++ b/json-log-viewer/jvm/src/main/scala/ru/d10xa/jsonlogviewer/decline/yaml/ConfigYamlLoaderImpl.scala @@ -23,9 +23,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { .trim private def parseOptionalQueryAST( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[QueryAST]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[QueryAST]] = parseOptionalStringField( fields, fieldName, @@ -38,9 +38,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseOptionalFormatIn( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[FormatIn]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[FormatIn]] = parseOptionalStringField( fields, fieldName, @@ -52,9 +52,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseOptionalListString( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[List[String]]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[List[String]]] = fields.get(fieldName) match { case Some(jsonValue) => jsonValue @@ -66,9 +66,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseOptionalFeeds( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[List[Feed]]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[List[Feed]]] = fields.get(fieldName) match { case Some(jsonValue) => jsonValue @@ -81,10 +81,10 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseOptionalStringField( - fields: Map[String, Json], - fieldName: String, - errorMsg: String - ): ValidatedNel[String, Option[String]] = + fields: Map[String, Json], + fieldName: String, + errorMsg: String + ): ValidatedNel[String, Option[String]] = fields.get(fieldName) match { case Some(jsonValue) => jsonValue.as[String].leftMap(_ => errorMsg).toValidatedNel.map(Some(_)) @@ -92,10 +92,10 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseString( - fields: Map[String, Json], - fieldName: String, - errorMsg: String - ): ValidatedNel[String, String] = + fields: Map[String, Json], + fieldName: String, + errorMsg: String + ): ValidatedNel[String, String] = fields.get(fieldName) match { case Some(j) => j.as[String].leftMap(_ => errorMsg).toValidatedNel @@ -104,9 +104,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseListString( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, List[String]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, List[String]] = fields.get(fieldName) match { case Some(c) => c.as[List[String]] @@ -117,9 +117,9 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { } private def parseOptionalString( - fields: Map[String, Json], - fieldName: String - ): ValidatedNel[String, Option[String]] = + fields: Map[String, Json], + fieldName: String + ): ValidatedNel[String, Option[String]] = fields.get(fieldName) match { case Some(c) => c.as[Option[String]] @@ -138,13 +138,20 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { "name" ) val commandsValidated = parseListString(feedFields, "commands") - val inlineInputValidated = parseOptionalString(feedFields, "inlineInput") + val inlineInputValidated = + parseOptionalString(feedFields, "inlineInput") val filterValidated = parseOptionalQueryAST(feedFields, "filter") val formatInValidated - : Validated[NonEmptyList[String], Option[FormatIn]] = + : Validated[NonEmptyList[String], Option[FormatIn]] = parseOptionalFormatIn(feedFields, "formatIn") - (nameValidated, commandsValidated, inlineInputValidated, filterValidated, formatInValidated) + ( + nameValidated, + commandsValidated, + inlineInputValidated, + filterValidated, + formatInValidated + ) .mapN(Feed.apply) } @@ -169,7 +176,7 @@ class ConfigYamlLoaderImpl extends ConfigYamlLoader { val formatInValidated: ValidatedNel[String, Option[FormatIn]] = parseOptionalFormatIn(fields, "formatIn") val commandsValidated - : ValidatedNel[String, Option[List[String]]] = + : ValidatedNel[String, Option[List[String]]] = parseOptionalListString(fields, "commands") val feedsValidated: ValidatedNel[String, Option[List[Feed]]] = parseOptionalFeeds(fields, "feeds") 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 31f20cc..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 @@ -32,7 +32,8 @@ class ShellImpl extends Shell { inlineInput: Option[String] ): fs2.Stream[IO, String] = { val streams = - commands.map(runInfiniteCommand) ++ inlineInput.map(Shell.stringToStream).toList + commands + .map(runInfiniteCommand) ++ inlineInput.map(Shell.stringToStream).toList fs2.Stream.emits(streams).parJoin(math.max(1, commands.length)) } 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 edbe328..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 @@ -108,7 +108,7 @@ object LogViewerStream { val baseStream = topCommandsOpt match { case Some(cmds) => commandsAndInlineInputToStream(cmds, None) - case None => + case None => stdinLinesStream } processStream(config, baseStream, None, None, None) 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 4541389..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 @@ -13,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: @@ -21,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/FormatOutValidator.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/decline/FormatOutValidator.scala index 885a5fa..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 @@ -8,7 +8,7 @@ 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 50c0b2e..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 @@ -12,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/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/shell/Shell.scala b/json-log-viewer/shared/src/main/scala/ru/d10xa/jsonlogviewer/shell/Shell.scala index f09f3a2..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 @@ -3,7 +3,10 @@ import cats.effect.IO import fs2.* trait Shell { - def mergeCommandsAndInlineInput(commands: List[String], inlineInput: Option[String]): Stream[IO, String] + def mergeCommandsAndInlineInput( + commands: List[String], + inlineInput: Option[String] + ): Stream[IO, String] } object Shell: From 6967086911bef0c0c2d6c0265dcec91d3d57dc8d Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 7 Jan 2025 21:12:00 +0300 Subject: [PATCH 14/15] remove Await from tests --- .../jsonlogviewer/TimestampFilterTest.scala | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 102d719..dc631c2 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,14 +1,14 @@ package ru.d10xa.jsonlogviewer import cats.effect.IO +import cats.effect.unsafe.implicits.* import fs2.Stream -import fs2.io.* -import cats.effect.syntax.all.* import munit.FunSuite -import cats.effect.unsafe.implicits.* -import scala.concurrent.duration.* + import java.time.ZonedDateTime -import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.Failure +import scala.util.Success class TimestampFilterTest extends FunSuite { test("filterTimestampAfter") { @@ -16,14 +16,19 @@ class TimestampFilterTest extends FunSuite { 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.unsafeToFuture() - - assertEquals( Await.result(list, 1.second), List(t2)) + ) + .compile + .toList + .unsafeToFuture() + .onComplete { + case Success(list) => assert(list == t2) + case Failure(exception) => fail(exception.getMessage) + } } def pr(ts: String): ParseResult = ParseResult( From a492f1ea530a519bf806403bb45e07f4e6be5866 Mon Sep 17 00:00:00 2001 From: Andrey Stolyarov Date: Tue, 7 Jan 2025 21:19:25 +0300 Subject: [PATCH 15/15] CatsEffectSuite --- build.sbt | 1 + .../d10xa/jsonlogviewer/TimestampFilterTest.scala | 14 +++----------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index e8fa93f..0637891 100644 --- a/build.sbt +++ b/build.sbt @@ -36,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, 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 dc631c2..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,16 +1,12 @@ package ru.d10xa.jsonlogviewer import cats.effect.IO -import cats.effect.unsafe.implicits.* import fs2.Stream -import munit.FunSuite +import munit.CatsEffectSuite import java.time.ZonedDateTime -import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.Failure -import scala.util.Success -class TimestampFilterTest extends FunSuite { +class TimestampFilterTest extends CatsEffectSuite { test("filterTimestampAfter") { val filter = TimestampFilter() val t1 = pr("2023-09-17T19:10:01.132318Z") @@ -24,11 +20,7 @@ class TimestampFilterTest extends FunSuite { ) .compile .toList - .unsafeToFuture() - .onComplete { - case Success(list) => assert(list == t2) - case Failure(exception) => fail(exception.getMessage) - } + .flatTap(list => IO(assertEquals(list, List(t2)))) } def pr(ts: String): ParseResult = ParseResult(