From 159c49d8484b659a0a6dfff5c1845b2b307dc618 Mon Sep 17 00:00:00 2001 From: Luke Stephenson Date: Wed, 11 Oct 2023 16:57:37 +1100 Subject: [PATCH] LogParser solutions --- .../scala/introcourse/level07/LogParser.scala | 53 +++++++++++++++---- src/main/scala/introcourse/level07/Main.scala | 6 ++- .../introcourse/level07/LogParserTest.scala | 4 +- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/main/scala/introcourse/level07/LogParser.scala b/src/main/scala/introcourse/level07/LogParser.scala index 87b5666..ab6b886 100644 --- a/src/main/scala/introcourse/level07/LogParser.scala +++ b/src/main/scala/introcourse/level07/LogParser.scala @@ -36,7 +36,9 @@ object LogParser { * - Error with (severity: Int) */ enum LogLevel { - case TODO // enums must have as least one case, replace this when you are ready + case Info + case Warning + case Error(severity: Int) } /** @@ -47,7 +49,8 @@ object LogParser { type Timestamp = Int enum LogMessage { - case TODO // enums must have as least one case, replace this when you are ready + case UnknownLog(message: String) + case KnownLog(logLevel: LogLevel, timestamp: Timestamp, message: String) } /** @@ -77,15 +80,27 @@ object LogParser { * Here is the beginning of one possible approach. **/ def parseLog(str: String): LogMessage = { + def buildLogMessage(logLevel: LogLevel, timestampStr: String, message: String): Option[LogMessage] = { + parseIntOption(timestampStr).map(timestamp => LogMessage.KnownLog(logLevel, timestamp, message)) + } + val fields = str.split(",").toList val optLog: Option[LogMessage] = fields match { case List("I", timestampStr, message) => - parseIntOption(timestampStr).map(timestamp => ???) - case _ => ??? // Add more cases + buildLogMessage(LogLevel.Info, timestampStr, message) + case List("W", timestampStr, message) => + buildLogMessage(LogLevel.Warning, timestampStr, message) + case List("E", severityStr, timestampStr, message) => + parseIntOption(severityStr).flatMap(severity => buildLogMessage(LogLevel.Error(severity), timestampStr, message)) + case _ => None } // What should we do if optLog is None? - ??? +// optLog match +// case Some(logMessage) => logMessage +// case None => LogMessage.UnknownLog(str) + + optLog.getOrElse(LogMessage.UnknownLog(str)) } /** @@ -96,7 +111,9 @@ object LogParser { * Hint: Convert an Array to a List with .toList * What if we get an empty line from the fileContent? */ - def parseLogFile(fileContent: String): List[LogMessage] = ??? + def parseLogFile(fileContent: String): List[LogMessage] = { + fileContent.split("\n").toList.filterNot(_.isEmpty).map(parseLog) + } /** * Define a function that returns only logs that are errors over the given severity level. @@ -108,7 +125,13 @@ object LogParser { * scala> getErrorsOverSeverity(List(KnownLog(Error(2), 123, some error msg")), 2) * > List() **/ - def getErrorsOverSeverity(logs: List[LogMessage], minimumSeverity: Int): List[LogMessage] = ??? + def getErrorsOverSeverity(logs: List[LogMessage], minimumSeverity: Int): List[LogMessage] = { + logs.filter { message => + message match + case LogMessage.KnownLog(LogLevel.Error(severity), _, _) if severity > minimumSeverity => true + case _ => false + } + } /** * Write a function to convert a `LogMessage` to a readable `String`. @@ -124,7 +147,17 @@ object LogParser { * * Hint: Pattern match and use string interpolation **/ - def showLogMessage(log: LogMessage): String = ??? + def showLogMessage(log: LogMessage): String = { + log match + case LogMessage.UnknownLog(message) => s"Unknown log: $message" + case LogMessage.KnownLog(logLevel, timestamp, message) => + val severityStr = logLevel match + case LogLevel.Info => "Info" + case LogLevel.Warning => "Warning" + case LogLevel.Error(severity) => s"Error $severity" + + s"$severityStr ($timestamp) $message" + } /** * Use `showLogMessage` on error logs with severity greater than the given `severity`. @@ -134,7 +167,9 @@ object LogParser { * * Hint: Use `parseLogFile`, `getErrorsOverSeverity` and `showLogMessage` **/ - def showErrorsOverSeverity(fileContent: String, severity: Int): List[String] = ??? + def showErrorsOverSeverity(fileContent: String, severity: Int): List[String] = { + getErrorsOverSeverity(parseLogFile(fileContent), severity).map(showLogMessage) + } /** * Now head over to `Main.scala` in the same package to complete the rest of the program. diff --git a/src/main/scala/introcourse/level07/Main.scala b/src/main/scala/introcourse/level07/Main.scala index c5a1eee..74e9475 100644 --- a/src/main/scala/introcourse/level07/Main.scala +++ b/src/main/scala/introcourse/level07/Main.scala @@ -22,7 +22,7 @@ object Main { val fileContent: String = Source.fromFile(filepath).getLines().mkString("\n") // Implement `printErrorsOverSeverity` and then call it from here - ??? + printErrorsOverSeverity(fileContent, 1) case _ => println("""sbt "runMain introcourse.level07.Main src/main/resources/logfile.csv"""") } @@ -39,6 +39,8 @@ object Main { * Hint: Use println to write to stdout */ @SuppressWarnings(Array("org.wartremover.warts.Any")) - private def printErrorsOverSeverity(logFile: String, severity: Int): Unit = ??? + private def printErrorsOverSeverity(logFile: String, severity: Int): Unit = { + showErrorsOverSeverity(logFile, severity).foreach(println) + } } diff --git a/src/test/scala/introcourse/level07/LogParserTest.scala b/src/test/scala/introcourse/level07/LogParserTest.scala index 9ba3548..eb0f20c 100644 --- a/src/test/scala/introcourse/level07/LogParserTest.scala +++ b/src/test/scala/introcourse/level07/LogParserTest.scala @@ -7,8 +7,8 @@ import org.scalactic.TypeCheckedTripleEquals import org.scalatest.funspec.AnyFunSpec class LogParserTest extends AnyFunSpec with TypeCheckedTripleEquals { - // TODO: Remove this import once you've defined the ADTs in LogParser - import Types.* +// // TODO: Remove this import once you've defined the ADTs in LogParser +// import Types.* describe("parseIntOption") { it("should return the parsed integer for a valid integer") {