Skip to content
Merged
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ lazy val core = projectMatrix
.settings(
snapshotsPackageName := "cue4s",
snapshotsIntegrations += SnapshotIntegration.MUnit,
snapshotsForceOverwrite := !sys.env.contains("CI"),
scalacOptions += "-Wunused:all",
scalacOptions += "-snippet-compiler:compile",
scalaJSUseMainModuleInitializer := true,
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
libraryDependencies += "com.lihaoyi" %%% "fansi" % Versions.fansi,
Expand Down Expand Up @@ -117,6 +119,7 @@ lazy val example = projectMatrix
nativeImageJvm := "graalvm-java23",
nativeImageVersion := "23.0.0",
nativeImageOptions ++= Seq("--install-exit-handlers", "--no-fallback"),
nativeImageReady := { () => sLog.value.info("Native image ready") },
),
)
.jsPlatform(
Expand Down
4 changes: 2 additions & 2 deletions modules/cats-effect/src/main/scala/PromptsIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object PromptsIO:
"0.0.5",
)
def apply(
out: Output = Output.Std,
out: Output = Output.StdNoLogging,
createTerminal: Output => Terminal = Terminal.ansi,
theme: Theme = Theme.Default,
symbols: Symbols = Symbols.platformDefault,
Expand All @@ -145,7 +145,7 @@ object PromptsIO:
end PromptsIO

class PromptsIOBuilder private (impl: IOPromptsOptions):
def this() = this(IOPromptsOptions(Output.Std, Theme.Default))
def this() = this(IOPromptsOptions(Output.StdNoLogging, Theme.Default))

def noColors: PromptsIOBuilder = withTheme(Theme.NoColors)
def withOutput(out: Output): PromptsIOBuilder = copy(_.copy(out = out))
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/main/scala-jvm-native/PlatformStd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cue4s

private[cue4s] trait PlatformStd extends Output:
override def logLn[A: AsString](a: A): Unit =
override def logLn[A: AsString](a: => A): Unit =
System.err.println(a.render)
System.err.flush()
override def out[A: AsString](a: A): Unit =
Expand Down
9 changes: 5 additions & 4 deletions modules/core/src/main/scala-jvm-native/SyncPrompts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package cue4s
import cue4s.Prompt.PasswordInput.Password

class SyncPromptsBuilder private (impl: SyncPromptsOptions):
def this() = this(SyncPromptsOptions(Output.Std, Theme.Default))
def this() = this(SyncPromptsOptions(Output.StdNoLogging, Theme.Default))

def noColors: SyncPromptsBuilder = withTheme(Theme.NoColors)
def withOutput(out: Output): SyncPromptsBuilder = copy(_.copy(out = out))
Expand All @@ -28,9 +28,10 @@ class SyncPromptsBuilder private (impl: SyncPromptsOptions):
def use[A](f: SyncPrompts => A): A =
val p = Prompts(impl.out, Terminal.ansi, impl.theme)
val inst = SyncPrompts(p)
try
f(inst)
finally p.close()
try f(inst)
finally
p.close()
impl.out.close()

private def copy(f: SyncPromptsOptions => SyncPromptsOptions) =
new SyncPromptsBuilder(f(impl))
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/main/scala/ASyncPrompts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import scala.concurrent.Future
import cue4s.Prompt.PasswordInput.Password

class AsyncPromptsBuilder private (impl: AsyncPromptsOptions):
def this() = this(AsyncPromptsOptions(Output.Std, Theme.Default))
def this() = this(AsyncPromptsOptions(Output.StdNoLogging, Theme.Default))

def noColors: AsyncPromptsBuilder = withTheme(Theme.NoColors)
def withOutput(out: Output): AsyncPromptsBuilder = copy(_.copy(out = out))
Expand Down
26 changes: 13 additions & 13 deletions modules/core/src/main/scala/AnsiTerminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AnsiTerminal(out: Output) extends Terminal:

private val writer = (s: String) => out.out(s)

private inline def call(name: Char, inline args: Int*): this.type =
private inline def csi(name: Char, inline args: Int*): this.type =
writer(s"$CSI${args.mkString(";")}$name")
this

Expand All @@ -33,10 +33,10 @@ class AnsiTerminal(out: Output) extends Terminal:
override inline def cursorShow(): this.type = call(s"$CSI?25h")

private inline def lineEraseMode(n: Int): this.type =
call('K', n)
csi('K', n)

private inline def screenEraseMode(n: Int): this.type =
call('K', n)
csi('K', n)

override inline def eraseEntireLine(): this.type = lineEraseMode(2)
override inline def eraseEntireScreen(): this.type = screenEraseMode(2)
Expand All @@ -47,20 +47,20 @@ class AnsiTerminal(out: Output) extends Terminal:
override inline def eraseToEndOfLine(): this.type = lineEraseMode(0)
override inline def eraseToEndOfScreen(): this.type = screenEraseMode(0)

override inline def moveBack(n: Int): this.type = call('D', n)
override inline def moveDown(n: Int): this.type = call('B', n)
override inline def moveForward(n: Int): this.type = call('C', n)
override inline def moveBack(n: Int): this.type = csi('D', n)
override inline def moveDown(n: Int): this.type = csi('B', n)
override inline def moveForward(n: Int): this.type = csi('C', n)
override inline def moveHorizontalTo(column: Int): this.type =
call('G', column)
override inline def moveNextLine(n: Int): this.type = call('E', n)
override inline def movePreviousLine(n: Int): this.type = call('F', n)
csi('G', column)
override inline def moveNextLine(n: Int): this.type = csi('E', n)
override inline def movePreviousLine(n: Int): this.type = csi('F', n)
override inline def moveToPosition(row: Int, column: Int): this.type =
call('H', row, column)
csi('H', row, column)
override inline def moveUp(n: Int): this.type =
call('A', n)
csi('A', n)

override inline def restore(): this.type = call('u')
override inline def save(): this.type = call('s')
override inline def restore(): this.type = call(s"${ESC}8")
override inline def save(): this.type = call(s"${ESC}7")
override inline def screenClear(): this.type = call(s"${ESC}c")

end AnsiTerminal
Expand Down
4 changes: 4 additions & 0 deletions modules/core/src/main/scala/CharCollector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ private[cue4s] object CharCollector:
b,
) =>
(State.CSI_Collecting(b.toByte :: Nil), DecodeResult.Continue)
case _ =>
error(
s"Unexpected byte ${char}, expected CSI parameter or intermediate byte",
)

case State.CSI_Collecting(bytes) =>
char match
Expand Down
41 changes: 41 additions & 0 deletions modules/core/src/main/scala/FileLogging.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2023 Neandertech
*
* Licensed 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
*
* http://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 cue4s

import java.io.File
import java.io.FileWriter

object FileLogging:
def toFile(originalOut: Output, file: String): Output =
createLoggingOut(originalOut, file)

def fromEnv(originalOut: Output, env: Map[String, String] = sys.env): Output =
env.get("CUE4S_LOG_FILE") match
case Some(value) =>
createLoggingOut(originalOut, value)
case None =>
originalOut

private def createLoggingOut(originalOut: Output, file: String): Output =
new Output:
private val fw = new FileWriter(new File(file))
override def logLn[A: AsString](a: => A): Unit =
fw.write(a.render + "\n")
fw.flush()
override def out[A: AsString](a: A): Unit = originalOut.out(a)
override def close(): Unit = fw.close()
end FileLogging
37 changes: 19 additions & 18 deletions modules/core/src/main/scala/InteractiveConfirmation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,28 @@ private[cue4s] class InteractiveConfirmation(
symbols: Symbols,
) extends PromptFramework[Boolean](terminal, out):

override type PromptState = Unit
override type PromptState = Boolean
override type Event = TerminalEvent

override def initialState: PromptState = ()
override def initialState: PromptState = default

override def extractResult(state: PromptState): Either[PromptError, Boolean] =
Right(state)

override def handleEvent(event: TerminalEvent): PromptAction =
event match
case TerminalEvent.Init => PromptAction.Update()
case TerminalEvent.Init => PromptAction.Continue
case TerminalEvent.Key(KeyEvent.ENTER) =>
PromptAction.setStatus(Status.Finished(default))
PromptAction.TrySubmit
case TerminalEvent.Char(which) =>
which match
case 'y' | 'Y' =>
PromptAction.setStatus(Status.Finished(true))
PromptAction.submit(true)
case 'n' | 'N' =>
PromptAction.setStatus(Status.Finished(false))
PromptAction.submit(false)
case _ => PromptAction.Continue
case TerminalEvent.Interrupt =>
PromptAction.setStatus(Status.Canceled)
PromptAction.Stop
case _ =>
PromptAction.Continue
end match
Expand All @@ -52,31 +55,29 @@ private[cue4s] class InteractiveConfirmation(
import theme.*

override def renderState(
state: Unit,
state: Boolean,
status: Status,
): List[String] =
val lines = List.newBuilder[String]
val lines = List.newBuilder[fansi.Str]
import symbols.*

status match
case Status.Init =>
lines += "? ".focused + prompt.prompt +
(s" $promptCue (" + (if default then "Y/n" else "y/N") + ")").hint
case Status.Running(error) =>
lines += "? ".focused + prompt.prompt +
lines += "? ".focused ++ prompt.prompt ++
(s" $promptCue (" + (if default then "Y/n" else "y/N") + ")").hint
error.left.toOption.foreach: err =>
lines += err.error
case Status.Finished(res) =>
lines += s"$promptDone ".focused + prompt.prompt + s" $ellipsis ".hint +
lines += s"$promptDone ".focused ++
prompt.prompt ++
s" $ellipsis ".hint ++
(if res then "yes" else "no").emphasis
case Status.Canceled =>
lines += s"$promptCancelled ".canceled + prompt.emphasis
lines += s"$promptCancelled ".canceled ++ prompt.emphasis
end match
// lines += ""

lines += ""

lines.result()
lines.result().map(_.render)
end renderState

end InteractiveConfirmation
26 changes: 12 additions & 14 deletions modules/core/src/main/scala/InteractiveMultipleChoice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ private[cue4s] class InteractiveMultipleChoice(
st: State,
status: Status,
): List[String] =
val lines = List.newBuilder[String]
val lines = List.newBuilder[fansi.Str]

import symbols.*

status match
case Status.Running(_) | Status.Init =>
lines += "? ".focused + lab.prompt + s" $promptCue ".prompt + st.text.input
lines += "Tab".emphasis + " to toggle, " + "Shift+Tab".emphasis + " to toggle all, " + "Enter".emphasis + " to submit."
case Status.Running(_) =>
lines += "? ".focused ++ lab.prompt ++ s" $promptCue ".prompt ++ st.text.input
lines += "Tab".emphasis ++ " to toggle, " ++ "Shift+Tab".emphasis ++ " to toggle all, " ++ "Enter".emphasis ++ " to submit."

status match
case Status.Running(Left(err)) =>
Expand Down Expand Up @@ -96,7 +96,7 @@ private[cue4s] class InteractiveMultipleChoice(
end match

case Status.Finished(ids) =>
lines += s"$promptDone ".focused + lab.prompt
lines += s"$promptDone ".focused ++ lab.prompt

if ids.isEmpty then lines += "nothing selected".nothingSelected
else
Expand All @@ -105,14 +105,16 @@ private[cue4s] class InteractiveMultipleChoice(
lines += s" $altSelected " + value.emphasis

case Status.Canceled =>
lines += s"$promptCancelled ".canceled +
(lab + " ").prompt
lines += s"$promptCancelled ".canceled ++ (lab + " ").prompt
lines += ""
end match

lines.result()
lines.result().map(_.render)
end renderState

override def extractResult(state: State): Either[PromptError, List[String]] =
Right(state.selected.toList.sorted.map(altMapping))

override def handleEvent(event: TerminalEvent) =
event match
case TerminalEvent.Key(KeyEvent.UP) =>
Expand All @@ -122,11 +124,7 @@ private[cue4s] class InteractiveMultipleChoice(
PromptAction.updateState(_.updateDisplay(_.down))

case TerminalEvent.Key(KeyEvent.ENTER) =>
PromptAction.setStatus(
Status.Finished(
currentState().selected.toList.sorted.map(altMapping),
),
)
PromptAction.TrySubmit

case TerminalEvent.Key(KeyEvent.TAB) =>
PromptAction.updateState(_.toggle)
Expand All @@ -141,7 +139,7 @@ private[cue4s] class InteractiveMultipleChoice(
PromptAction.updateState(_.addText(which.toChar))

case TerminalEvent.Interrupt =>
PromptAction.setStatus(Status.Canceled)
PromptAction.Stop

case _ =>
PromptAction.Continue
Expand Down
Loading
Loading