mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 11:14:55 +08:00
add reporter module, add modules table log on start
This commit is contained in:
parent
4fb08f6240
commit
c5fef1359d
@ -8,7 +8,7 @@ object MornyConfiguration {
|
||||
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
|
||||
val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
|
||||
|
||||
val VERSION = "2.0.0-alpha7"
|
||||
val VERSION = "2.0.0-alpha8"
|
||||
val VERSION_DELTA: Option[String] = None
|
||||
val CODENAME = "guanggu"
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
package cc.sukazyo.cono.morny
|
||||
|
||||
import cc.sukazyo.cono.morny.bot.command.MornyCommandManager
|
||||
import cc.sukazyo.cono.morny.daemon.MornyDaemons
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||
import cc.sukazyo.cono.morny.MornyCoeur.*
|
||||
import cc.sukazyo.cono.morny.bot.api.EventListenerManager
|
||||
import cc.sukazyo.cono.morny.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
|
||||
import cc.sukazyo.cono.morny.bot.query.MornyQueryManager
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.util.schedule.Scheduler
|
||||
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||
import cc.sukazyo.cono.morny.util.time.WatchDog
|
||||
@ -111,6 +111,14 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
given MornyCoeur = this
|
||||
|
||||
val externalContext: GivenContext = GivenContext()
|
||||
import util.dataview.Table.format as fmtTable
|
||||
logger info
|
||||
s"""The following Modules have been added to current Morny:
|
||||
|${fmtTable(
|
||||
("Module ID" :: "Module Name" :: "Module Version" :: Nil)::Nil :::
|
||||
modules.map(f => f.id :: f.name :: f.version :: Nil)
|
||||
).replaceAll("\n", "\n|")}
|
||||
|""".stripMargin
|
||||
|
||||
///>>> BLOCK START instance configure & startup stage 1
|
||||
|
||||
@ -156,8 +164,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
/** current Morny's [[MornyTrusted]] instance */
|
||||
val trusted: MornyTrusted = MornyTrusted()
|
||||
|
||||
val daemons: MornyDaemons = MornyDaemons()
|
||||
initializeContext << daemons
|
||||
val eventManager: EventListenerManager = EventListenerManager()
|
||||
val commands: MornyCommandManager = MornyCommandManager()
|
||||
val queries: MornyQueryManager = MornyQueryManager()
|
||||
@ -203,7 +209,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
eventManager, commands, queries,
|
||||
initializeContext)))
|
||||
|
||||
eventManager register daemons.reporter.EventStatistics.EventInfoCatcher
|
||||
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
|
||||
import cc.sukazyo.cono.morny.util.CommonFormat.formatDuration as f
|
||||
logger warn
|
||||
@ -234,7 +239,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
modules.foreach(it => it.onStarting(OnStartingContext(
|
||||
initializeContext)))
|
||||
|
||||
daemons.start()
|
||||
logger info "start telegram event listening"
|
||||
import com.pengrad.telegrambot.TelegramException
|
||||
account.setUpdatesListener(eventManager, (e: TelegramException) => {
|
||||
@ -254,7 +258,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
| server responses:
|
||||
|${GsonBuilder().setPrettyPrinting().create.toJson(e.response) indent 4}
|
||||
|""".stripMargin
|
||||
this.daemons.reporter.exception(e, "Failed get updates.")
|
||||
externalContext.consume[MornyReport](_.exception(e, "Failed get updates."))
|
||||
}
|
||||
|
||||
if (e.getCause != null) {
|
||||
@ -278,7 +282,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
logger error
|
||||
s"""Failed get updates:
|
||||
|${exceptionLog(e_other) indent 3}""".stripMargin
|
||||
this.daemons.reporter.exception(e_other, "Failed get updates.")
|
||||
externalContext.consume[MornyReport](_.exception(e_other, "Failed get updates."))
|
||||
}
|
||||
|
||||
})
|
||||
@ -291,7 +295,6 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
logger info "resetting telegram command list"
|
||||
commands.automaticTGListUpdate()
|
||||
|
||||
daemons.reporter.reportCoeurMornyLogin()
|
||||
initializeContext = null
|
||||
logger info "Coeur start complete."
|
||||
|
||||
@ -303,17 +306,26 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
||||
}
|
||||
|
||||
private def exitCleanup (): Unit = {
|
||||
daemons.reporter.reportCoeurExit()
|
||||
|
||||
// Morny Exiting
|
||||
modules.foreach(it => it.onExiting)
|
||||
account.shutdown()
|
||||
logger info "stopped bot account"
|
||||
daemons.stop()
|
||||
|
||||
account.removeGetUpdatesListener()
|
||||
logger info "stopped bot update listener"
|
||||
tasks.waitForStop()
|
||||
logger info s"morny tasks stopped: remains ${tasks.amount} tasks not be executed"
|
||||
|
||||
// Morny Exiting Post
|
||||
if config.commandLogoutClear then
|
||||
commands.automaticTGListRemove()
|
||||
modules.foreach(it => it.onExitingPost)
|
||||
|
||||
account.shutdown()
|
||||
logger info "stopped bot account"
|
||||
// Morny Exited
|
||||
modules.foreach(it => it.onExited)
|
||||
logger info "done exit cleanup"
|
||||
logger info "done exit cleanup\nMorny will EXIT now"
|
||||
|
||||
}
|
||||
|
||||
private def configure_exitCleanup (): Unit = {
|
||||
|
@ -20,6 +20,7 @@ trait MornyModule {
|
||||
def onRoutineSavingData (using MornyCoeur): Unit = {}
|
||||
|
||||
def onExiting (using MornyCoeur): Unit = {}
|
||||
def onExitingPost (using MornyCoeur): Unit = {}
|
||||
def onExited (using MornyCoeur): Unit = {}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import java.util.TimeZone
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.language.postfixOps
|
||||
|
||||
object ServerMain {
|
||||
object ServerMain {
|
||||
|
||||
val tz: TimeZone = TimeZone getDefault
|
||||
val tz_offset: ZoneOffset = ZoneOffset ofTotalSeconds (tz.getRawOffset/1000)
|
||||
|
@ -16,7 +16,8 @@ object ServerModulesLoader {
|
||||
social_share.ModuleSocialShare(),
|
||||
medication_timer.ModuleMedicationTimer(),
|
||||
morny_misc.ModuleMornyMisc(),
|
||||
uni_meow.ModuleUniMeow()
|
||||
uni_meow.ModuleUniMeow(),
|
||||
reporter.Module()
|
||||
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.bot.api
|
||||
|
||||
import cc.sukazyo.cono.morny.{Log, MornyCoeur}
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.pengrad.telegrambot.model.Update
|
||||
@ -87,7 +88,7 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
||||
) indent 4) ++= "\n"
|
||||
case _ =>
|
||||
logger error errorMessage.toString
|
||||
coeur.daemons.reporter.exception(e, "on event running")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "on event running"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package cc.sukazyo.cono.morny.bot.command
|
||||
import cc.sukazyo.cono.morny.{MornyCoeur, MornySystem}
|
||||
import cc.sukazyo.cono.morny.data.MornyInformation.*
|
||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
|
||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramParseEscape.escapeHtml as h
|
||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
||||
@ -162,13 +163,17 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
|
||||
}
|
||||
|
||||
private def echoEventStatistics (using update: Update): Unit = {
|
||||
coeur.account exec SendMessage(
|
||||
update.message.chat.id,
|
||||
// language=html
|
||||
s"""<b>Event Statistics :</b>
|
||||
|in today
|
||||
|${coeur.daemons.reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
|
||||
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
|
||||
coeur.externalContext >> { (reporter: MornyReport) =>
|
||||
coeur.account exec SendMessage(
|
||||
update.message.chat.id,
|
||||
// language=html
|
||||
s"""<b>Event Statistics :</b>
|
||||
|in today
|
||||
|${reporter.EventStatistics.eventStatisticsHTML}""".stripMargin
|
||||
).parseMode(ParseMode.HTML).replyToMessageId(update.message.messageId)
|
||||
} || {
|
||||
echo404
|
||||
}
|
||||
}
|
||||
|
||||
private def echo404 (using event: Update): Unit =
|
||||
|
@ -3,7 +3,7 @@ import cc.sukazyo.cono.morny.bot.command.ICommandAlias.HiddenAlias
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
||||
import cc.sukazyo.cono.morny.Log.logger
|
||||
import cc.sukazyo.cono.morny.daemon.MornyReport
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||
@ -41,7 +41,7 @@ class MornyManagers (using coeur: MornyCoeur) {
|
||||
TelegramStickers ID_403
|
||||
).replyToMessageId(event.message.messageId)
|
||||
logger attention s"403 exit caught from user ${user toLogTag}"
|
||||
coeur.daemons.reporter.unauthenticatedAction("/exit", user)
|
||||
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/exit", user))
|
||||
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ class MornyManagers (using coeur: MornyCoeur) {
|
||||
TelegramStickers ID_403
|
||||
).replyToMessageId(event.message.messageId)
|
||||
logger attention s"403 save caught from user ${user toLogTag}"
|
||||
coeur.daemons.reporter.unauthenticatedAction("/save", user)
|
||||
coeur.externalContext.consume[MornyReport](_.unauthenticatedAction("/save", user))
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
package cc.sukazyo.cono.morny.daemon
|
||||
|
||||
import cc.sukazyo.cono.morny.Log.logger
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
|
||||
class MornyDaemons (using val coeur: MornyCoeur) {
|
||||
|
||||
val reporter: MornyReport = MornyReport()
|
||||
|
||||
def start (): Unit = {
|
||||
|
||||
logger notice "ALL Morny Daemons starting..."
|
||||
|
||||
reporter.start()
|
||||
|
||||
logger notice "Morny Daemons started."
|
||||
|
||||
}
|
||||
|
||||
def stop (): Unit = {
|
||||
|
||||
logger notice "stopping All Morny Daemons..."
|
||||
|
||||
reporter.stop()
|
||||
|
||||
logger notice "stopped ALL Morny Daemons."
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package cc.sukazyo.cono.morny.data
|
||||
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||
import cc.sukazyo.cono.morny.MornyAssets
|
||||
import cc.sukazyo.cono.morny.daemon.MornyReport
|
||||
import cc.sukazyo.cono.morny.MornyAssets.AssetsException
|
||||
|
||||
import java.io.IOException
|
||||
|
@ -5,6 +5,7 @@ import cc.sukazyo.cono.morny.MornyCoeur
|
||||
import cc.sukazyo.cono.morny.bot.command.{ICommandAlias, ITelegramCommand}
|
||||
import cc.sukazyo.cono.morny.bot.command.ICommandAlias.ListedAlias
|
||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
||||
import cc.sukazyo.cono.morny.util.CommonEncrypt
|
||||
import cc.sukazyo.cono.morny.util.CommonEncrypt.*
|
||||
@ -81,7 +82,7 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
|
||||
_r.document.fileName
|
||||
)} catch case e: IOException =>
|
||||
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
|
||||
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
|
||||
return
|
||||
} else if ((_r ne null) && (_r.photo ne null)) {
|
||||
try {
|
||||
@ -102,11 +103,11 @@ class Encryptor (using coeur: MornyCoeur) extends ITelegramCommand {
|
||||
case e: IOException =>
|
||||
//noinspection DuplicatedCode
|
||||
logger warn s"NetworkRequest error: TelegramFileAPI:\n\t${e.getMessage}"
|
||||
coeur.daemons.reporter.exception(e, "NetworkRequest error: TelegramFileAPI")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "NetworkRequest error: TelegramFileAPI"))
|
||||
return
|
||||
case e: IllegalArgumentException =>
|
||||
logger warn s"FileProcess error: PhotoSize:\n\t${e.getMessage}"
|
||||
coeur.daemons.reporter.exception(e, "FileProcess error: PhotoSize")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "FileProcess error: PhotoSize"))
|
||||
return
|
||||
} else if ((_r ne null) && (_r.text ne null)) {
|
||||
XText(_r.text)
|
||||
|
70
src/main/scala/cc/sukazyo/cono/morny/reporter/Module.scala
Normal file
70
src/main/scala/cc/sukazyo/cono/morny/reporter/Module.scala
Normal file
@ -0,0 +1,70 @@
|
||||
package cc.sukazyo.cono.morny.reporter
|
||||
|
||||
import cc.sukazyo.cono.morny.internal.MornyInternalModule
|
||||
import cc.sukazyo.cono.morny.Log.logger
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
|
||||
class Module extends MornyInternalModule {
|
||||
|
||||
override val id: String = "morny.report"
|
||||
override val name: String = "Morny/Coeur Reporter"
|
||||
override val description: String | Null =
|
||||
"""Report crucial messages to a Telegram channel.
|
||||
|""".stripMargin
|
||||
|
||||
description.take(description.indexOf("\n"))
|
||||
|
||||
override def onInitializingPre (using MornyCoeur)(cxt: MornyCoeur.OnInitializingPreContext): Unit = {
|
||||
import cxt.*
|
||||
|
||||
val instance = MornyReport()
|
||||
externalContext << instance
|
||||
givenCxt << instance
|
||||
|
||||
}
|
||||
|
||||
override def onInitializing (using MornyCoeur)(cxt: MornyCoeur.OnInitializingContext): Unit = {
|
||||
import cxt.*
|
||||
|
||||
externalContext >> { (instance: MornyReport) =>
|
||||
logger info "MornyReport will now collect your bot event statistics."
|
||||
eventManager register instance.EventStatistics.EventInfoCatcher
|
||||
} || {
|
||||
logger warn "There seems no reporter instance is provided; skipped register events for it."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override def onStarting (using coeur: MornyCoeur)(cxt: MornyCoeur.OnStartingContext): Unit = {
|
||||
import coeur.externalContext
|
||||
externalContext >> { (instance: MornyReport) =>
|
||||
instance.start()
|
||||
} || {
|
||||
logger warn "There seems no reporter instance is provided; skipped start it."
|
||||
}
|
||||
}
|
||||
|
||||
override def onStartingPost (using coeur: MornyCoeur)(cxt: MornyCoeur.OnStartingPostContext): Unit = {
|
||||
import coeur.externalContext
|
||||
externalContext >> { (instance: MornyReport) =>
|
||||
instance.reportCoeurMornyLogin()
|
||||
}
|
||||
}
|
||||
|
||||
override def onExiting (using coeur: MornyCoeur): Unit = {
|
||||
import coeur.externalContext
|
||||
externalContext >> { (instance: MornyReport) =>
|
||||
instance.stop()
|
||||
} || {
|
||||
logger warn "There seems no reporter instance need to be stop."
|
||||
}
|
||||
}
|
||||
|
||||
override def onExitingPost (using coeur: MornyCoeur): Unit = {
|
||||
import coeur.externalContext
|
||||
externalContext >> { (instance: MornyReport) =>
|
||||
instance.reportCoeurExit()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cc.sukazyo.cono.morny.daemon
|
||||
package cc.sukazyo.cono.morny.reporter
|
||||
|
||||
import cc.sukazyo.cono.morny.{MornyCoeur, MornyConfig}
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
@ -6,6 +6,7 @@ import cc.sukazyo.cono.morny.data.TelegramStickers
|
||||
import cc.sukazyo.cono.morny.social_share.event.OnGetSocial.tryFetchSocial
|
||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import cc.sukazyo.cono.morny.social_share.api.{SocialTwitterParser, SocialWeiboParser}
|
||||
import cc.sukazyo.cono.morny.social_share.external.{twitter, weibo}
|
||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Message.entitiesSafe
|
||||
@ -87,7 +88,7 @@ object OnGetSocial {
|
||||
).replyToMessageId(replyToMessage)
|
||||
logger error
|
||||
"Error on requesting FixTweet API\n" + exceptionLog(e)
|
||||
coeur.daemons.reporter.exception(e, "Error on requesting FixTweet API")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting FixTweet API"))
|
||||
|
||||
def tryFetchSocialOfWeibo (url: weibo.StatusUrlInfo)(using replyChat: Long, replyToMessage: Int)(using coeur: MornyCoeur) =
|
||||
import cc.sukazyo.cono.morny.social_share.external.weibo.MApi
|
||||
@ -111,6 +112,6 @@ object OnGetSocial {
|
||||
).replyToMessageId(replyToMessage)
|
||||
logger error
|
||||
"Error on requesting Weibo m.API\n" + exceptionLog(e)
|
||||
coeur.daemons.reporter.exception(e, "Error on requesting Weibo m.API")
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e, "Error on requesting Weibo m.API"))
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import cc.sukazyo.cono.morny.MornyCoeur
|
||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
|
||||
import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
|
||||
import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, ITelegramQuery}
|
||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||
import com.pengrad.telegrambot.model.Update
|
||||
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
|
||||
|
||||
@ -17,7 +18,6 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
|
||||
private val ID_PREFIX_BILI_AV = "[morny/share/bili/av]"
|
||||
private val ID_PREFIX_BILI_BV = "[morny/share/bili/bv]"
|
||||
private val LINK_PREFIX = "https://bilibili.com/video/"
|
||||
private val REGEX_BILI_VIDEO: Regex = "^(?:(?:https?://)?(?:www\\.)?bilibili\\.com(?:/s)?/video/((?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))/?(\\?(?:p=(\\d+))?.*)?|(?:av|AV)(\\d{1,12})|(?:bv|BV)([A-HJ-NP-Za-km-z1-9]{10}))$"r
|
||||
private val SHARE_FORMAT_HTML = "<a href='%s'>%s</a>"
|
||||
|
||||
override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
|
||||
@ -37,7 +37,7 @@ class ShareToolBilibili (using coeur: MornyCoeur) extends ITelegramQuery {
|
||||
return null;
|
||||
case e: IllegalStateException =>
|
||||
logger error exceptionLog(e)
|
||||
coeur.daemons.reporter.exception(e)
|
||||
coeur.externalContext.consume[MornyReport](_.exception(e))
|
||||
return null;
|
||||
|
||||
val av = result.av
|
||||
|
@ -0,0 +1,20 @@
|
||||
package cc.sukazyo.cono.morny.util.dataview
|
||||
|
||||
object Table {
|
||||
|
||||
def format (table: Seq[Seq[Any]]): String = {
|
||||
if (table.isEmpty) ""
|
||||
else {
|
||||
// Get column widths based on the maximum cell width in each column (+2 for a one character padding on each side)
|
||||
val colWidths = table.transpose.map(_.map(cell => if (cell == null) 0 else cell.toString.length).max + 2)
|
||||
// Format each row
|
||||
val rows = table.map(_.zip(colWidths).map { case (item, size) => (" %-" + (size - 1) + "s").format(item) }
|
||||
.mkString("|", "|", "|"))
|
||||
// Formatted separator row, used to separate the header and draw table borders
|
||||
val separator = colWidths.map("-" * _).mkString("+", "+", "+")
|
||||
// Put the table together and return
|
||||
(separator +: rows.head +: separator +: rows.tail :+ separator).mkString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user