This is an automated email from the ASF dual-hosted git repository.

aradzinski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/master by this push:
     new 460bf6c  WIP migration
460bf6c is described below

commit 460bf6c341be11307305d897fe7ce7846bd9e861
Author: Aaron Radzinski <[email protected]>
AuthorDate: Tue Oct 5 11:47:48 2021 -0700

    WIP migration
---
 .../nlpcraft/common/ansi/NCAnsiProgressBar.scala   | 132 ++++++++++++++++++
 .../nlpcraft/common/ansi/NCAnsiSpinner.scala       | 112 +++++++++++++++
 .../org/apache/nlpcraft/common/util/NCUtils.scala  | 150 ++++++++++++++++++++-
 3 files changed, 393 insertions(+), 1 deletion(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
new file mode 100644
index 0000000..dc8e5f2
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.common.ansi
+
+import java.io.PrintWriter
+import org.apache.nlpcraft.common._
+import NCAnsi._
+import org.apache.commons.lang3.StringUtils
+import org.apache.nlpcraft.common.ansi.NCAnsiProgressBar._
+
+/**
+  * Forward-only, bound ANSI-based progress bar.
+  *
+  * @param out
+  * @param totalTicks Number of ticks to complete.
+  * @param dispSize Visual size of the progress bar.
+  * @param clearOnComplete
+  * @param useAnsi
+  */
+class NCAnsiProgressBar(
+    out: PrintWriter,
+    totalTicks: Int,
+    dispSize: Int,
+    clearOnComplete: Boolean = true,
+    useAnsi: Boolean = true):
+    require(dispSize <= totalTicks)
+
+    @volatile private var tick = 0
+
+    private final val mux = new Object()
+    private final val PB_LEFT = s"$B${CHAR_SET.head}$RST"
+    private final val PB_RIGHT = s"$B${CHAR_SET(3)}$RST"
+    private final val PB_EMPTY = s"$W${CHAR_SET(2)}$RST"
+    private final val PB_FULL = s"$R$BO${CHAR_SET(1)}$RST"
+    private final val PB_LEAD = s"$Y$BO${CHAR_SET(4)}$RST"
+
+    /**
+      *
+      */
+    private def clean(): Unit =
+        out.print(ansiCursorLeft * (dispSize + 2/* Left & right brackets. */ + 
5/* % string. */))
+        out.print(ansiClearLineAfter)
+        out.flush()
+
+    /**
+      * Starts progress bar.
+      */
+    def start(): Unit =
+        tick = 0
+        if useAnsi then
+            mux.synchronized {
+                // Hide cursor to avoid blinking.
+                out.print(ansiCursorHide)
+                out.print(PB_LEFT)
+                out.print(PB_EMPTY * dispSize)
+                out.print(PB_RIGHT)
+                out.print(" ")
+                out.print(s"${W}0%  $RST")
+                out.flush()
+            }
+
+    /**
+      * Ticks progress bar one tick at a time.
+      */
+    def ticked(): Unit =
+        mux.synchronized {
+            tick += 1
+
+            if useAnsi then
+                clean()
+                val ratio = tick.toFloat / totalTicks.toFloat
+                val bar = if (tick == 1) 1 else Math.round(ratio * dispSize)
+                val pct = Math.round(ratio * 100)
+                out.print(PB_LEFT)
+                for (i <- 0 until dispSize) {
+                    if (i < bar)
+                        out.print(PB_FULL)
+                    else if (i == bar)
+                        out.print(PB_LEAD)
+                    else
+                        out.print(PB_EMPTY)
+                }
+                out.print(PB_RIGHT)
+                out.print(" ")
+                out.print(W + StringUtils.rightPad(s"$pct%",4) + RST)
+                out.flush()
+            else if tick == 1 || tick % (totalTicks / dispSize) == 0 then
+                out.print(NON_ANSI_CHAR)
+                out.flush()
+        }
+
+    /**
+      * Whether progress is complete.
+      *
+      * @return
+      */
+    def completed: Boolean = tick == totalTicks
+
+    /**
+      * Stops progress bar.
+      */
+    def stop(): Unit =
+        if useAnsi && clearOnComplete then mux.synchronized {
+            clean()
+
+            // Show cursor.
+            out.print(ansiCursorShow)
+            out.flush()
+        }
+
+/**
+  *
+  */
+object NCAnsiProgressBar:
+    // Active charset to use.
+    private final val NON_ANSI_CHAR = '='
+    private val CHAR_SET = Seq('[', '=', '.', ']', '>')
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala
new file mode 100644
index 0000000..1a4bb4a
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.common.ansi
+
+import java.io.PrintWriter
+import NCAnsi.*
+import org.apache.nlpcraft.common.*
+import org.apache.nlpcraft.common.ansi.NCAnsiSpinner.*
+import org.apache.nlpcraft.common.util.NCUtils
+
+/**
+  * ANSI-based hourglass spinner.
+  *
+  * @param out
+  * @param useAnsi
+  */
+class NCAnsiSpinner(out: PrintWriter, useAnsi: Boolean = true):
+    @volatile private var thread: Thread = _
+    @volatile private var suffix = ""
+    @volatile private var prefix = ""
+    @volatile private var lastLength = 0
+    @volatile private var frame = 0
+
+    private final val mux = new Object()
+
+    /**
+      *
+      * @param p
+      */
+    def setSuffix(p: String): Unit =
+        this.suffix = if (p == null) "" else p
+
+    /**
+      *
+      * @param p
+      */
+    def setPrefix(p: String): Unit =
+        this.prefix = if (p == null) "" else p
+
+    /**
+      *
+      */
+    private def clean(): Unit =
+        out.print(ansiCursorLeft * lastLength)
+        out.print(ansiClearLineAfter)
+        out.flush()
+
+    /**
+      * Starts spinner.
+      */
+    def start(): Unit =
+        if useAnsi then
+            thread = NCUtils.mkThread("ansi-spinner") { t =>
+                frame = 0
+                lastLength = 0
+                // Hide cursor to avoid blinking.
+                out.print(ansiCursorHide)
+                out.flush()
+                while (!t.isInterrupted)
+                    mux.synchronized {
+                        if frame > 0 then clean()
+                        out.print(s"$prefix$ansiCyanFg${CHAR_SET(frame % 
CHAR_SET.size)}$ansiReset$suffix")
+                        out.flush()
+                    }
+                    lastLength = NCUtils.stripAnsi(prefix).length + 1 + 
NCUtils.stripAnsi(suffix).length
+                    frame += 1
+                    Thread.sleep(1000 / CHAR_SET.size) // Full rotation per 
second.
+            }
+
+            thread.start()
+        else
+            mux.synchronized {
+                out.print("... ")
+                out.flush()
+            }
+
+    /**
+      * Stops spinner.
+      */
+    def stop(): Unit =
+        NCUtils.stopThread(thread)
+
+        if useAnsi && frame > 0 then
+            mux.synchronized {
+                clean()
+
+                // Show cursor.
+                out.print(ansiCursorShow)
+                out.flush()
+            }
+
+/**
+  *
+  */
+object NCAnsiSpinner:
+    // An active charset to use.
+    private final val CHAR_SET = Seq('-', '\\', '|', '/')
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index 330cc94..f512afe 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -17,7 +17,8 @@
 
 package org.apache.nlpcraft.common.util
 
-import com.typesafe.scalalogging.LazyLogging
+import com.typesafe.scalalogging.{LazyLogging, Logger}
+import org.apache.nlpcraft.common.NCException
 import org.apache.nlpcraft.common.ansi.NCAnsi.*
 
 import java.util.Random
@@ -411,3 +412,150 @@ object NCUtils extends LazyLogging:
 
             sb.toString()
 
+    /**
+      *
+      * @param logger
+      * @param title
+      * @param e
+      */
+    def prettyError(logger: Logger, title: String, e: Throwable): Unit =
+        // Keep the full trace in the 'trace' log level.
+        logger.trace(title, e)
+
+        prettyErrorImpl(new PrettyErrorLogger {
+            override def log(s: String): Unit = logger.error(s)
+        }, title, e)
+
+
+    /**
+      *
+      * @param title
+      * @param e
+      */
+    def prettyError(title: String, e: Throwable): Unit = prettyErrorImpl(new 
PrettyErrorLogger(), title, e)
+
+    sealed class PrettyErrorLogger:
+        def log(s: String): Unit = System.err.println(s)
+
+    /**
+      *
+      * @param logger
+      * @param title
+      * @param e
+      */
+    private def prettyErrorImpl(logger: PrettyErrorLogger, title: String, e: 
Throwable): Unit =
+        logger.log(title)
+
+        val INDENT = 2
+        var x = e
+        var indent = INDENT
+        while (x != null)
+            var first = true
+            var errMsg = x.getLocalizedMessage
+            if errMsg == null then errMsg = "<null>"
+            val exClsName = if !x.isInstanceOf[NCException] then 
s"$ansiRedFg[${x.getClass.getCanonicalName}]$ansiReset " else ""
+            val trace = 
x.getStackTrace.find(!_.getClassName.startsWith("scala.")).getOrElse(x.getStackTrace.head)
+            val fileName = trace.getFileName
+            val lineNum = trace.getLineNumber
+            val msg =
+                if fileName == null || lineNum < 0 then
+                    s"$exClsName$errMsg"
+                else
+                    s"$exClsName$errMsg $ansiCyanFg->$ansiReset 
($fileName:$lineNum)"
+
+            msg.split("\n").foreach(line => {
+                val s = s"${" " * indent}${if (first) ansiBlue("+-+ ") else "  
 "}${bo(y(line))}"
+                logger.log(s)
+                first = false
+            })
+
+            val traces = x.getStackTrace.filter { t =>
+                val mtdName = t.getMethodName
+                val clsName = t.getClassName
+
+                // Clean up trace.
+                clsName.startsWith("org.apache.nlpcraft") &&
+                    
!clsName.startsWith("org.apache.nlpcraft.common.opencensus") &&
+                    !mtdName.contains("startScopedSpan") &&
+                    !mtdName.contains('$')
+            }
+            for (trace <- traces)
+                val fileName = trace.getFileName
+                val lineNum = trace.getLineNumber
+                val mtdName = trace.getMethodName
+                val clsName = 
trace.getClassName.replace("org.apache.nlpcraft", "o.a.n")
+
+                logger.log(s"${" " * indent}  ${b("|")} $clsName.$mtdName 
$ansiCyanFg->$ansiReset ($fileName:$lineNum)")
+
+            indent += INDENT
+
+            x = x.getCause
+
+    /**
+      * Makes thread.
+      *
+      * @param name Name.
+      * @param body Thread body.
+      */
+    def mkThread(name: String)(body: Thread => Unit): Thread =
+        new Thread(name):
+            @volatile private var stopped = false
+
+            override def isInterrupted: Boolean = super.isInterrupted || 
stopped
+            override def interrupt(): Unit =  stopped = true; super.interrupt()
+
+            override def run(): Unit =
+                logger.trace(s"Thread started: $name")
+
+                try
+                    body(this)
+                    logger.trace(s"Thread exited: $name")
+
+                catch
+                    case _: InterruptedException => logger.trace(s"Thread 
interrupted: $name")
+                    case e: Throwable => prettyError(logger, s"Unexpected 
error during '$name' thread execution:", e)
+                finally
+                    stopped = true
+
+    /**
+      * Makes thread.
+      *
+      * @param name Name.
+      * @param body Thread body.
+      */
+    def mkThread(name: String, body: Runnable): Thread =
+        mkThread(name) { _ => body.run() }
+
+    /**
+      * Sleeps number of milliseconds properly handling exceptions.
+      *
+      * @param delay Number of milliseconds to sleep.
+      */
+    def sleep(delay: Long): Unit =
+        try
+            Thread.sleep(delay)
+        catch
+            case _: InterruptedException => Thread.currentThread().interrupt()
+            case e: Throwable => prettyError(logger, "Unhandled exception 
caught during sleep:", e)
+
+
+    /**
+      * Interrupts thread and waits for its finish.
+      *
+      * @param t Thread.
+      */
+    def stopThread(t: Thread): Unit =
+        if t != null then
+            t.interrupt()
+            try
+                t.join()
+            catch
+                case _: InterruptedException => logger.trace("Thread joining 
was interrupted (ignoring).")
+
+    /**
+      * Interrupts thread.
+      *
+      * @param t Thread.
+      */
+    def interruptThread(t: Thread): Unit = if t != null then t.interrupt()
+

Reply via email to