From 3c9ad0ee5698554b9311b3fbc63d3e33873830c6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Tue, 29 Sep 2026 16:52:51 -0500 Subject: [PATCH] Docs(fix): Complete wrapper first-use examples Give Kotlin and Scala readers complete build files and programs with explicit configuration, private tmux servers, and cleanup. Compile and invoke the documented main functions without fixture imports or variables, and reject placeholder Scala install versions. Verified the required full check on JDK 25 and tmux 3.7c with Bash startup files disabled. Published alpha.17 consumer programs also pass on tmux 3.2a and 3.7c; deliberate import, configuration, assertion, and version defects fail the documentation gates. --- CHANGELOG.md | 5 + .../GenerateScalaDocumentationSuite.kt | 20 ++- docs/README.md | 9 ++ docs/guide/kotlin.md | 24 ++-- docs/guide/scala/compatibility.md | 4 +- docs/guide/scala/getting-started.md | 127 ++++++++---------- .../libtmux/docs/DocumentationFactsTest.java | 6 +- libtmux-kotlin/README.md | 84 +++++++++++- libtmux-kotlin/build.gradle.kts | 48 +++++-- libtmux-scala-cats/README.md | 2 +- libtmux-scala-ox/README.md | 2 +- libtmux-scala/README.md | 2 +- 12 files changed, 220 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbc4cb7b..d44753169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ production. ### Documented +- **Kotlin and Scala getting-started guides include complete programs.** Each + selects a private socket, creates tmux objects, and stops its owned daemon. + The documentation tests execute their entry points, and Scala installation + examples name an exact package version. + - **`SplitSpec.Builder.running`, `WindowSpec.Builder.running` and `SessionSpec.Builder.running` say that tmux reports the pane before the command has started.** Wait for the pane's command with `Pane.await` rather diff --git a/build-logic/conventions/src/main/kotlin/io/github/libtmux/buildlogic/GenerateScalaDocumentationSuite.kt b/build-logic/conventions/src/main/kotlin/io/github/libtmux/buildlogic/GenerateScalaDocumentationSuite.kt index 41f51d165..41e80fc29 100644 --- a/build-logic/conventions/src/main/kotlin/io/github/libtmux/buildlogic/GenerateScalaDocumentationSuite.kt +++ b/build-logic/conventions/src/main/kotlin/io/github/libtmux/buildlogic/GenerateScalaDocumentationSuite.kt @@ -24,7 +24,8 @@ import org.gradle.api.tasks.TaskAction * cannot stop compiling or running unnoticed. * * Each fence carries a directive on the line before it, ``: - * `sync` runs the code against a fresh tmux, `io` runs a Cats `IO` to completion within the + * `sync` runs the code against a fresh tmux, `main` invokes a standalone `Main.main`, + * and `io` runs a Cats `IO` to completion within the * 15-second deadline every documented snippet has, `reject` asserts the code fails to compile with * `detail` in the error, and `build` marks an sbt build fragment the consumer checks exercise * instead. An unclassified fence fails the build, and so @@ -87,7 +88,7 @@ abstract class GenerateScalaDocumentationSuite : DefaultTask() { val beforeIndex = (start - 1 downTo 0).firstOrNull { lines[it].isNotBlank() } val before = beforeIndex?.let { lines[it].trim() }.orEmpty() val directive = requireNotNull(DIRECTIVE.matchEntire(before)) { - "$where has an unclassified Scala fence; use a snippet: scala-sync, scala-io, scala-reject or scala-build directive" + "$where has an unclassified Scala fence; use a snippet: scala-sync, scala-main, scala-io, scala-reject or scala-build directive" } unusedDirectives.remove(beforeIndex) val (mode, id, detail) = directive.destructured @@ -111,7 +112,7 @@ abstract class GenerateScalaDocumentationSuite : DefaultTask() { } snippets } - require(found.any { it.mode == "sync" || it.mode == "io" }) { "no runnable Scala documentation fences were discovered" } + require(found.any { it.mode in setOf("sync", "main", "io") }) { "no runnable Scala documentation fences were discovered" } val duplicates = found.groupBy { it.id }.filterValues { it.size > 1 }.keys.sorted() require(duplicates.isEmpty()) { "duplicate Scala documentation ids: $duplicates" } return found @@ -123,14 +124,19 @@ abstract class GenerateScalaDocumentationSuite : DefaultTask() { .joinToString(",\n") { quoted(relative(root, it)) + " -> " + quoted(digest(it.readBytes())) } val names = runnable.joinToString(",\n") { quoted(it.name(root)) } val declarations = runnable.filterNot { it.mode == "reject" }.joinToString("\n") { snippet -> - val body = if (snippet.mode == "sync") "DocumentationRuntime.requireUnit {\n${snippet.code}\n}" else "{\n${snippet.code}\n}" - val result = if (snippet.mode == "sync") "Unit" else "_root_.cats.effect.IO[Unit]" - "private[docs] object ${snippet.objectName} {\ndef run(config: io.github.libtmux.ServerConfig): $result = $body\n}\n" + if (snippet.mode == "main") { + "private[docs] object ${snippet.objectName} {\n${snippet.code}\ndef run(): Unit = Main.main(Array.empty[String])\n}\n" + } else { + val body = if (snippet.mode == "sync") "DocumentationRuntime.requireUnit {\n${snippet.code}\n}" else "{\n${snippet.code}\n}" + val result = if (snippet.mode == "sync") "Unit" else "_root_.cats.effect.IO[Unit]" + "private[docs] object ${snippet.objectName} {\ndef run(config: io.github.libtmux.ServerConfig): $result = $body\n}\n" + } } val cases = runnable.joinToString("\n") { snippet -> val invoke = snippet.objectName + ".run(fixture.config)" val body = when (snippet.mode) { "sync" -> "io.github.libtmux.scaladsl.fixture.OwnedTmux.use { fixture => $invoke }" + "main" -> "${snippet.objectName}.run()" "io" -> "io.github.libtmux.scaladsl.fixture.OwnedTmux.use { fixture =>\n" + "val evaluated = new java.util.concurrent.atomic.AtomicBoolean(false)\n" + @@ -175,7 +181,7 @@ $cases val IGNORED = setOf(".git", ".gradle", ".bsp", ".metals", ".idea", "target", "build", "node_modules") val OPENING = Regex("^ {0,3}(`{3,}|~{3,})[ \\t]*([^\\s`]*).*$") val UNSUPPORTED_SCALA_FENCE = Regex("(?i)^[ \\t>]*(?:`{3,}|~{3,})[ \\t]*(?:scala\\S*|sbt)(?:[ \\t].*)?$") - val DIRECTIVE = Regex("^$") + val DIRECTIVE = Regex("^$") fun relative(root: File, file: File): String = root.toPath().relativize(file.toPath()).toString().replace('\\', '/') diff --git a/docs/README.md b/docs/README.md index 5230d5565..f703898aa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -250,6 +250,15 @@ the task. Only one direction is checked: a Kotlin fence that uses a name it did not ask for fails to compile, but one that asks for a name it never reads is not caught, because the generated file suppresses unused-variable warnings. +A complete Kotlin program uses ``; the generated test +compiles it as a separate file, without fixture imports, and calls its +`main()` function. A complete Scala program uses +`` and defines `object Main` with +`def main(args: Array[String]): Unit`. Its generated test invokes that entry +point without supplying fixture variables. These programs supply their own +configuration and must clean up their +private tmux servers. + ### Claims that are not code A snippet is executed, so it cannot lie. A version in an install block, or a diff --git a/docs/guide/kotlin.md b/docs/guide/kotlin.md index dbf6ca753..48fde2dbd 100644 --- a/docs/guide/kotlin.md +++ b/docs/guide/kotlin.md @@ -1,5 +1,9 @@ # Kotlin +Start with the [complete program](../../libtmux-kotlin/README.md#create-and-clean-up-a-private-session) +for configuration and cleanup. This guide explains coroutine behavior and DSL +scopes when using those same handles. + ## Wrapper classes over the Java handles `Server`, `Session`, `Window`, `Pane`, `Client`, and `ControlClient` in @@ -81,7 +85,9 @@ leaving it running past the point nothing is listening for its result. - `ControlClient.output`/`events` hold no thread at all; only collecting the returned `Flow` reads. -## The DSL, and the compile error the first draft had + + +## Scope nested session builders `@DslMarker` marks `SessionBuilder`, `WindowBuilder`, and `SplitBuilder` so an inner block cannot reach an outer block's receiver by accident. Directory is @@ -105,26 +111,14 @@ withServer(config) { server -> } ``` -tmux's `SplitSpec.Builder` spells direction and size as method calls — -`below()`/`above()`/`toRight()`/`toLeft()`, `cells(n)`/`percent(n)` — not an -enum. The DSL forwards to that real vocabulary directly rather than inventing -a parallel `SplitDirection`/`PaneSize` type. +Use `below()`/`above()`/`toRight()`/`toLeft()` to choose split direction and +`cells(n)`/`percent(n)` to choose its size. `env(name, value)` is on `SessionBuilder`, `WindowBuilder`, and `SplitBuilder` alike, matching the Java `SessionSpec.Builder`/`WindowSpec.Builder`/ `SplitSpec.Builder` each has, and sets a variable in the environment the new session, window, or pane's process starts with. -## Why nothing in Java may depend on this - -Nothing written in Java may depend on `libtmux-kotlin`, and the build fails if -it does. Per the JSpecify specification a class carrying `@kotlin.Metadata` is -*not* null-marked, because the Kotlin compiler does not yet emit full -nullness into binaries -([KT-47417](https://youtrack.jetbrains.com/projects/KT/issues/KT-47417/Emit-jspecify-annotations-for-types-in-Kotlin-binaries)). -A Kotlin-authored API would therefore be worse for a Java caller and invisible -to NullAway. The dependency runs one way only. - See the [module README](../../libtmux-kotlin/README.md) for the full call-site tour, including the query DSL, the exhaustive `when` over sealed failures, and `StateFlow`. diff --git a/docs/guide/scala/compatibility.md b/docs/guide/scala/compatibility.md index d44c29f72..1d5afe777 100644 --- a/docs/guide/scala/compatibility.md +++ b/docs/guide/scala/compatibility.md @@ -41,8 +41,8 @@ operations are written by hand, for their cancellation or resource scoping: Both facades generate from the same catalog through the same owner-and-kind branching, and the [generator's tests][generator-tests] hold the two to it: a captured operation forwards purely on both, and a mutation is effect-wrapped on -the Cats side only. Nothing Scala has shipped yet, so there is no earlier -release for MiMa or `tasty-mima` to compare against. +the Cats side only. These alpha artifacts have no binary compatibility +guarantee; pin an exact version when adding them to an application. ## Inherited feature boundaries diff --git a/docs/guide/scala/getting-started.md b/docs/guide/scala/getting-started.md index 7a2697970..f3b46af39 100644 --- a/docs/guide/scala/getting-started.md +++ b/docs/guide/scala/getting-started.md @@ -14,113 +14,102 @@ They are on Maven Central and release with the Java artifacts. - **`libtmux-scala-cats_3`** — Cats Effect resources and FS2 observations. - **`libtmux-scala-ox_3`** — an Ox `Flow` over subscriptions and live views. +For a new sbt project, save this as `build.sbt`: + ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "" +scalaVersion := "3.9.0" +scalacOptions += "-release:25" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "0.0.1-alpha.17" ``` For Cats Effect and FS2, or for Ox, add the matching module: ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala-cats" % "" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala-cats" % "0.0.1-alpha.17" ``` ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala-ox" % "" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala-ox" % "0.0.1-alpha.17" ``` From Gradle or Maven, name the suffixed artifact directly: -`io.github.libtmux:libtmux-scala_3:`. The core facade depends on +`io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17`. The core facade depends on `libtmux` and the Scala 3 library, and on nothing else. ## A first client -The function below takes three caller-supplied values and constructs a Java -`ServerConfig` before opening the Scala client: - -| Parameter | Value to supply | -| --- | --- | -| `binary` | Absolute path to your tmux executable | -| `socket` | An isolated socket you own under `/tmp/libtmux-java-dev/` | -| `configFile` | Your tmux configuration file, or `/dev/null` for none | - -For example, choose `/tmp/libtmux-java-dev/scala-start/socket`; the function -creates its parent directory. Opening the client does not create tmux; -`newSession` does. The operation creates a session, splits its window, verifies -the resulting panes and kills that session. Closing the client is separate -from session cleanup. An existing server's other sessions remain running; -tmux normally exits when its final session closes. +Save this complete program as `src/main/scala/Main.scala`. It selects a fresh +socket under `/tmp/libtmux-java-dev/`, creates a session with two panes, checks +the captured layout, and stops its private daemon before closing the client. +It reads tmux from `PATH`; set `LIBTMUX_TMUX` to select another binary. -Call `firstClient` with your three values from your application. -The final call in this tested snippet takes those inputs from the owned test -fixture's `config`; the function builds and uses its own configuration. - - + ```scala import io.github.libtmux.{ Layout, ServerConfig, ServerEndpoint, SessionSpec, SplitSpec } -import io.github.libtmux.scaladsl.{config => _, *} +import io.github.libtmux.scaladsl.* import java.nio.file.{Files, Path} -import java.time.Duration import scala.util.Using -def firstClient(binary: String, socket: Path, configFile: Path): Unit = { - require(Path.of(binary).isAbsolute, "supply an absolute tmux executable") - val ownedSocket = socket.toAbsolutePath.normalize() - require( - ownedSocket.startsWith(Path.of("/tmp/libtmux-java-dev")) || - ownedSocket.startsWith(Path.of("/tmp/libtmux-java-test")), - "choose a socket under an owned libtmux Java directory" - ) - Files.createDirectories(ownedSocket.getParent) - val selected = ServerConfig.builder() - .binary(binary) - .endpoint(ServerEndpoint.socketPath(ownedSocket)) - .configFile(configFile) - .defaultTimeout(Duration.ofMillis(800)) - .build() - - Using.resource(Server.open(selected)) { server => - val session = server.newSession( - SessionSpec.builder().named("scala-start").running("cat", "-").build() - ) +object Main { + def main(args: Array[String]): Unit = { + val root = Files.createDirectories(Path.of("/tmp/libtmux-java-dev")) + val directory = Files.createTempDirectory(root, "scala-start-") + val socket = directory.resolve("s") + val config = ServerConfig.builder() + .binary(sys.env.getOrElse("LIBTMUX_TMUX", "tmux")) + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .build() try { - val window = session.windows.head - val second = window.split( - SplitSpec.builder().running("cat", "-").build() - ) - window.selectLayout(Layout.EVEN_HORIZONTAL) - second.select() - // window.panes is CAPTURED: it answers from window's own frozen capture, taken before the - // split, so this refreshes first rather than reading stale data. - val panes = window.refresh().panes - assert(panes.size == 2) - assert(panes.exists(_.info.id().value() == second.info.id().value())) - } finally session.kill() + Using.resource(Server.open(config)) { server => + try { + val session = server.newSession( + SessionSpec.builder().named("scala-start").running("cat", "-").build() + ) + val window = session.windows.head + val second = window.split( + SplitSpec.builder().running("cat", "-").build() + ) + window.selectLayout(Layout.EVEN_HORIZONTAL) + second.select() + val panes = window.refresh().panes + assert(panes.size == 2) + assert(panes.exists(_.info.id().value() == second.info.id().value())) + println(s"created ${panes.size} panes") + } finally server.killServer() + } + } finally { + Files.deleteIfExists(socket) + Files.deleteIfExists(directory) + } } } +``` -config.endpoint() match { - case endpoint: ServerEndpoint.SocketPath => - firstClient( - config.binaryPath(), - endpoint.path(), - config.configFile().orElse(Path.of("/dev/null")) - ) - case _ => throw new IllegalArgumentException("an explicit socket is required") -} +Run it with sbt: + +```console +$ sbt run ``` +The output includes `created 2 panes`. `window.refresh()` captures the pane +list after the split; the original window's captured list still has one pane. +`Using.resource` closes the library client. The separate `killServer` call +stops the daemon because this program created and owns it. When connecting to +an existing daemon, leave its lifetime with its owner and remove only sessions +your application created. + Timeouts are `scala.concurrent.duration.FiniteDuration` at every public entry point, converted once at the boundary: `pane.awaitText("$", 5.seconds)` never surfaces `java.time.Duration` to the caller. Follow with [queries](query.md), [ownership](ownership.md), then -[execution](execution.md). For immediate access without the facade, use the -[direct Java guide](../scala.md). +[execution](execution.md). The [API reference][server] documents the direct-style server. ## Build from source diff --git a/docs/src/test/java/io/github/libtmux/docs/DocumentationFactsTest.java b/docs/src/test/java/io/github/libtmux/docs/DocumentationFactsTest.java index 177106a69..a1b6ad1bb 100644 --- a/docs/src/test/java/io/github/libtmux/docs/DocumentationFactsTest.java +++ b/docs/src/test/java/io/github/libtmux/docs/DocumentationFactsTest.java @@ -184,9 +184,8 @@ void theReadmeNamesTheEndsOfTheMatrixItClaims() { * Every sbt install line the documentation shows is one the sbt consumer build resolves. * *

That build fetches each artifact from the staged repository exactly as a reader's would, so a - * documented line it does not carry is a line nothing has shown to work. Versions are compared - * as a placeholder: the documents say {@code ""} or the release, the build says - * {@code libtmuxVersion}. + * documented line it does not carry is a line nothing has shown to work. Documents name the + * release; the consumer receives that version through {@code libtmuxVersion}. */ @Test void everyDocumentedSbtLineIsOneTheSbtConsumerResolves() { @@ -198,6 +197,7 @@ void everyDocumentedSbtLineIsOneTheSbtConsumerResolves() { for (String document : readerFacing()) { Matcher found = fence.matcher(read(document)); while (found.find()) { + assertFalse(found.group(2).contains("\"\""), document + " needs an installable version"); for (String line : sbtLines(found.group(2))) { if (!consumer.contains(line)) { unresolved.add(document + " (" + found.group(1) + "): " + line); diff --git a/libtmux-kotlin/README.md b/libtmux-kotlin/README.md index 6c53853f3..1c13e5bae 100644 --- a/libtmux-kotlin/README.md +++ b/libtmux-kotlin/README.md @@ -25,20 +25,96 @@ the 2.2 standard library, the level kotlinx-coroutines 1.11 is compiled for, and a Kotlin compiler reads metadata up to one minor version newer than itself. `ConsumerBaselineTest` fails if a build raises that level. -Every Kotlin example below is executed against a real tmux server by -[`ReadmeExamplesTest`](src/test/kotlin/io/github/libtmux/kotlin/ReadmeExamplesTest.kt), -one test per section. +The [documentation tests](../docs/README.md#kotlin-fences) compile and run +the Kotlin examples against a real tmux server. ## Install - +Use JDK 25 or newer, tmux 3.2a through 3.7c, and a Gradle Kotlin/JVM +project with its Gradle wrapper. Name the project in `settings.gradle.kts`: + + +```kotlin +rootProject.name = "tmux-demo" +``` + +Save this as `build.gradle.kts`: + + ```kotlin +plugins { + kotlin("jvm") version "2.4.10" + application +} + +repositories { mavenCentral() } + +kotlin { jvmToolchain(25) } + dependencies { implementation(platform("io.github.libtmux:libtmux-bom:0.0.1-alpha.17")) implementation("io.github.libtmux:libtmux-kotlin") } + +application { mainClass = "MainKt" } ``` +## Create and clean up a private session + +Save this complete program as `src/main/kotlin/Main.kt`. It creates a fresh +socket directory under `/tmp/libtmux-java-dev/`, starts its own tmux server, +prints the created session's name, and removes that server before closing the +client. It reads tmux from `PATH`; set `LIBTMUX_TMUX` to select another binary. + + +```kotlin +import io.github.libtmux.ServerConfig +import io.github.libtmux.ServerEndpoint +import io.github.libtmux.kotlin.* +import java.nio.file.Files +import java.nio.file.Path +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + val root = Files.createDirectories(Path.of("/tmp/libtmux-java-dev")) + val directory = Files.createTempDirectory(root, "kotlin-start-") + val socket = directory.resolve("s") + val config = ServerConfig.builder() + .binary(System.getenv("LIBTMUX_TMUX") ?: "tmux") + .endpoint(ServerEndpoint.socketPath(socket)) + .configFile(Path.of("/dev/null")) + .build() + try { + withServer(config) { server -> + try { + val session = server.newSession("kotlin-start") + check(session.name == "kotlin-start") + println(session.name) + } finally { + server.killServer() + } + } + } finally { + Files.deleteIfExists(socket) + Files.deleteIfExists(directory) + } +} +``` + +Run the application: + +```console +$ ./gradlew run +``` + +The output includes `kotlin-start`. `withServer` closes the library client; +it does not stop tmux. This program calls `killServer` because it owns the +private daemon. When connecting to an existing daemon, leave its lifetime +with its owner and remove only sessions your application created. + +Later examples declare the `config` they expect. Supply the configuration +above and retain its private-server cleanup when trying those calls. + ## Wrapper classes, not the Java types directly `Server`, `Session`, `Window`, `Pane`, `Client`, and `ControlClient` here are diff --git a/libtmux-kotlin/build.gradle.kts b/libtmux-kotlin/build.gradle.kts index f6a06b443..83342857d 100644 --- a/libtmux-kotlin/build.gradle.kts +++ b/libtmux-kotlin/build.gradle.kts @@ -207,6 +207,9 @@ val documentedKotlin = fence.findAll(text).forEach { match -> val directive = match.groupValues[1] if (directive.startsWith("skip:")) return@forEach + require(directive.isBlank() || directive == "main") { + "$where has an unknown Kotlin snippet directive: $directive" + } found++ val line = text.substring(0, match.range.first).count { it == '\n' } + 1 @@ -215,6 +218,30 @@ val documentedKotlin = // the same line would otherwise generate one function twice. val name = "${where.replace('/', ' ').replace('.', ' ')} line $line" + if (directive == "main") { + val packageName = "io.github.libtmux.docs.kotlin.s$found" + val directory = generated.get().asFile.resolve(packageName.replace('.', '/')) + directory.mkdirs() + // Separate files keep harness imports out of a complete reader program. + directory.resolve("Main.kt").writeText("package $packageName\n\n${match.groupValues[2]}") + directory.resolve("Snippet.kt").writeText( + """ + |package $packageName + | + |import org.junit.jupiter.api.Test + | + |class Snippet { + | @Test + | fun `$name`() { + | main() + | } + |} + | + """.trimMargin(), + ) + return@forEach + } + // A shown result becomes an assertion, the same rule the documentation module applies to the // Java fences: what a reader sees after the arrow is what toString produced, so // documentation cannot claim a value the library does not give. @@ -282,17 +309,18 @@ val documentedKotlin = appendLine() appendLine("package io.github.libtmux.docs.kotlin.s$found") appendLine() + // Import the facade by default; importing the core wildcard too + // would make identically named handles ambiguous. + imports += listOf( + "import io.github.libtmux.kotlin.*", + "import io.github.libtmux.junit5.TmuxExtension", + "import io.github.libtmux.junit5.TmuxSocketPath", + "import kotlin.test.assertEquals", + "import kotlinx.coroutines.runBlocking", + "import org.junit.jupiter.api.Test", + "import org.junit.jupiter.api.extension.ExtendWith", + ) imports.forEach { appendLine(it) } - // The Kotlin facade, by default — this module's own surface, not the Java - // core it wraps (a wildcard import of both would make Pane/Session/Window/ - // Client ambiguous). A snippet naming a Java-only type imports it itself. - appendLine("import io.github.libtmux.kotlin.*") - appendLine("import io.github.libtmux.junit5.TmuxExtension") - appendLine("import io.github.libtmux.junit5.TmuxSocketPath") - appendLine("import kotlin.test.assertEquals") - appendLine("import kotlinx.coroutines.runBlocking") - appendLine("import org.junit.jupiter.api.Test") - appendLine("import org.junit.jupiter.api.extension.ExtendWith") appendLine() append( """ diff --git a/libtmux-scala-cats/README.md b/libtmux-scala-cats/README.md index 057059eea..e96b9e296 100644 --- a/libtmux-scala-cats/README.md +++ b/libtmux-scala-cats/README.md @@ -22,7 +22,7 @@ as `libtmux-scala`. ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala-cats" % "" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala-cats" % "0.0.1-alpha.17" ``` From Gradle or Maven the coordinate is `io.github.libtmux:libtmux-scala-cats_3`. diff --git a/libtmux-scala-ox/README.md b/libtmux-scala-ox/README.md index 0c402e9db..55fa82a36 100644 --- a/libtmux-scala-ox/README.md +++ b/libtmux-scala-ox/README.md @@ -23,7 +23,7 @@ client's subscription, and a live view of a server. ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala-ox" % "" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala-ox" % "0.0.1-alpha.17" ``` From Gradle or Maven the coordinate is `io.github.libtmux:libtmux-scala-ox_3`. diff --git a/libtmux-scala/README.md b/libtmux-scala/README.md index f13267436..1462450dc 100644 --- a/libtmux-scala/README.md +++ b/libtmux-scala/README.md @@ -38,7 +38,7 @@ suites against. See [Compatibility](../docs/guide/scala/compatibility.md). ```sbt -libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "" +libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "0.0.1-alpha.17" ``` From Gradle or Maven the coordinate is `io.github.libtmux:libtmux-scala_3`.