+added console spport for lyngio
+added console support to lyng/jlyng CLI +added unicode escapes +created tetris console sample
This commit is contained in:
1 parent
1a080bc53e
commit
d9d7cafec8
44 files changed
+3842
-28
No files matched your search
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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 net.sergeych.lyng.io.console
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.sergeych.lyng.ExecutionError
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.ObjBool
|
||||
import net.sergeych.lyng.obj.ObjIllegalOperationException
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessOp
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.fs.security.AccessContext
|
||||
import net.sergeych.lyngio.fs.security.AccessDecision
|
||||
import net.sergeych.lyngio.fs.security.Decision
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class LyngConsoleModuleTest {
|
||||
|
||||
private fun newScope(): Scope = Scope.new()
|
||||
|
||||
@Test
|
||||
fun installIsIdempotent() = runBlocking {
|
||||
val scope = newScope()
|
||||
assertTrue(createConsoleModule(PermitAllConsoleAccessPolicy, scope))
|
||||
assertFalse(createConsoleModule(PermitAllConsoleAccessPolicy, scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moduleSmokeScript() = runBlocking {
|
||||
val scope = newScope()
|
||||
createConsoleModule(PermitAllConsoleAccessPolicy, scope)
|
||||
|
||||
val code = """
|
||||
import lyng.io.console
|
||||
import lyng.stdlib
|
||||
|
||||
val d = Console.details()
|
||||
assert(d.supported is Bool)
|
||||
assert(d.isTty is Bool)
|
||||
assert(d.ansiLevel is String)
|
||||
|
||||
val g = Console.geometry()
|
||||
if (g != null) {
|
||||
assert(g.columns is Int)
|
||||
assert(g.rows is Int)
|
||||
assert(g.columns > 0)
|
||||
assert(g.rows > 0)
|
||||
}
|
||||
|
||||
assert(Console.events() is Iterable)
|
||||
Console.write("")
|
||||
Console.flush()
|
||||
Console.home()
|
||||
Console.clear()
|
||||
Console.moveTo(1, 1)
|
||||
Console.clearLine()
|
||||
Console.enterAltScreen()
|
||||
Console.leaveAltScreen()
|
||||
Console.setCursorVisible(true)
|
||||
|
||||
val changed = Console.setRawMode(false)
|
||||
assert(changed is Bool)
|
||||
true
|
||||
""".trimIndent()
|
||||
|
||||
val result = scope.eval(code)
|
||||
assertIs<ObjBool>(result)
|
||||
assertTrue(result.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun denyWritePolicyMapsToIllegalOperation() {
|
||||
runBlocking {
|
||||
val denyWritePolicy = object : ConsoleAccessPolicy {
|
||||
override suspend fun check(op: ConsoleAccessOp, ctx: AccessContext): AccessDecision = when (op) {
|
||||
is ConsoleAccessOp.WriteText -> AccessDecision(Decision.Deny, "denied by test policy")
|
||||
else -> AccessDecision(Decision.Allow)
|
||||
}
|
||||
}
|
||||
|
||||
val scope = newScope()
|
||||
createConsoleModule(denyWritePolicy, scope)
|
||||
|
||||
val error = kotlin.test.assertFailsWith<ExecutionError> {
|
||||
scope.eval(
|
||||
"""
|
||||
import lyng.io.console
|
||||
Console.write("x")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
assertIs<ObjIllegalOperationException>(error.errorObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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 net.sergeych.lyngio.console
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MordantLyngConsoleJvmTest {
|
||||
|
||||
@Test
|
||||
fun basicCapabilitiesSmoke() = runBlocking {
|
||||
val console = getSystemConsole()
|
||||
assertNotNull(console)
|
||||
|
||||
// Must be callable in any environment (interactive or redirected output).
|
||||
val tty = console.isTty()
|
||||
val ansi = console.ansiLevel()
|
||||
val geometry = console.geometry()
|
||||
|
||||
if (geometry != null) {
|
||||
assertTrue(geometry.columns > 0, "columns must be positive when geometry is present")
|
||||
assertTrue(geometry.rows > 0, "rows must be positive when geometry is present")
|
||||
}
|
||||
|
||||
// no-op smoke checks
|
||||
console.write("")
|
||||
console.flush()
|
||||
|
||||
// Keep values live so compiler doesn't optimize away calls in future changes
|
||||
assertNotNull(ansi)
|
||||
assertTrue(tty || !tty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setRawModeContract() = runBlocking {
|
||||
val console = getSystemConsole()
|
||||
val enabledChanged = console.setRawMode(true)
|
||||
val disabledChanged = console.setRawMode(false)
|
||||
|
||||
// If enabling changed state, disabling should also change it back.
|
||||
if (enabledChanged) {
|
||||
assertTrue(disabledChanged, "raw mode disable should report changed after enable")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun eventsSourceDoesNotCrash() = runBlocking {
|
||||
val console = getSystemConsole()
|
||||
val source = console.events()
|
||||
val event = source.nextEvent(350)
|
||||
source.close()
|
||||
// Any event kind is acceptable in this smoke test; null is also valid when idle.
|
||||
if (event != null) {
|
||||
assertTrue(
|
||||
event is ConsoleEvent.Resize || event is ConsoleEvent.KeyDown || event is ConsoleEvent.KeyUp,
|
||||
"unexpected event type: ${event::class.simpleName}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user