diff --git a/cli/pom.xml b/cli/pom.xml index 32020dac..105ac15e 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -71,7 +71,7 @@ com.github.ajalt.clikt clikt-jvm - 3.5.4 + 4.2.0 diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt index 4de3fb98..1008770b 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/CliOptions.kt @@ -63,7 +63,7 @@ internal interface WithConfluenceServerOptions { accessToken != null && confluenceUser != null -> throw PrintMessage("Both access token and username/password specified, but only one of them allowed") accessToken != null -> TokenAuth(accessToken!!) confluenceUser != null -> passwordAuth(confluenceUser!!, confluencePassword) - else -> throw PrintMessage("Either access token or username/password should be specified", error = true) + else -> throw PrintMessage("Either access token or username/password should be specified", printError = true) } private fun passwordAuth(username: String, password: String?): PasswordAuth { diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/ClicktExt.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/ClicktExt.kt index a1498eeb..b09e8336 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/ClicktExt.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/ClicktExt.kt @@ -1,31 +1,34 @@ package com.github.zeldigas.text2confl.cli +import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.PrintMessage -import com.github.ajalt.clikt.parameters.options.FlagOption +import com.github.ajalt.clikt.core.terminal +import com.github.ajalt.clikt.parameters.options.NullableOption import com.github.ajalt.clikt.parameters.options.RawOption -import com.github.ajalt.clikt.parameters.options.switch +import com.github.ajalt.clikt.parameters.options.nullableFlag +import com.github.ajalt.mordant.terminal.ConfirmationPrompt +import com.github.ajalt.mordant.terminal.StringPrompt import com.github.zeldigas.text2confl.cli.upload.InvalidTenantException import com.github.zeldigas.text2confl.convert.ConversionFailedException import com.github.zeldigas.text2confl.convert.FileDoesNotExistException fun parameterMissing(what: String, cliOption: String, fileOption: String): Nothing { - throw PrintMessage("$what is not specified. Use `$cliOption` option or `$fileOption` in config file", error = true) + throw PrintMessage("$what is not specified. Use `$cliOption` option or `$fileOption` in config file", printError = true) } fun parameterMissing(what: String, cliOption: String): Nothing { - throw PrintMessage("$what is not specified. Use `$cliOption` option", error = true) + throw PrintMessage("$what is not specified. Use `$cliOption` option", printError = true) } -fun RawOption.optionalFlag(vararg secondaryNames: String): FlagOption { - val allOptions = names.map { it to true } + secondaryNames.map { it to false } - return switch(allOptions.toMap()) +fun RawOption.optionalFlag(vararg secondaryNames: String): NullableOption { + return nullableFlag(*secondaryNames) } fun tryHandleException(ex: Exception) : Nothing { when (ex) { - is InvalidTenantException -> throw PrintMessage(ex.message!!, error = true) - is FileDoesNotExistException -> throw PrintMessage(ex.message!!, error = true) + is InvalidTenantException -> throw PrintMessage(ex.message!!, printError = true) + is FileDoesNotExistException -> throw PrintMessage(ex.message!!, printError = true) is ConversionFailedException -> { val reason = buildString { append(ex.message) @@ -33,12 +36,22 @@ fun tryHandleException(ex: Exception) : Nothing { append(" (cause: ${ex.cause})") } } - throw PrintMessage("Failed to convert ${ex.file}: $reason", error = true) + throw PrintMessage("Failed to convert ${ex.file}: $reason", printError = true) } is ContentValidationFailedException -> { val issues = ex.errors.mapIndexed { index, error -> "${index + 1}. $error"}.joinToString(separator = "\n") - throw PrintMessage("Some pages content is invalid:\n${issues}", error = true) + throw PrintMessage("Some pages content is invalid:\n${issues}", printError = true) } else -> throw ex } } + +fun CliktCommand.promptForSecret(prompt:String, requireConfirmation: Boolean): String? { + return if(requireConfirmation) { + ConfirmationPrompt.create(prompt, "Repeat for confirmation: ") { + StringPrompt(it, terminal, hideInput = true) + }.ask() + }else{ + return terminal.prompt(prompt, hideInput = true) + } +} diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt index 6858ac2e..5d525608 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/DumpToMarkdown.kt @@ -63,5 +63,5 @@ class DumpToMarkdown : CliktCommand(name = "export-to-md", help = "Exports confl } override fun askForSecret(prompt: String, requireConfirmation: Boolean): String? = - prompt(prompt, hideInput = true, requireConfirmation = true) + promptForSecret(prompt, requireConfirmation) } diff --git a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt index cc4c0086..cb0a54b9 100644 --- a/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt +++ b/cli/src/main/kotlin/com/github/zeldigas/text2confl/cli/Upload.kt @@ -117,13 +117,13 @@ class Upload : CliktCommand(name = "upload", help = "Converts source files and u private fun passwordAuth(username: String, password: String?): PasswordAuth { val effectivePassword = password - ?: prompt("Enter password: ", hideInput = true, requireConfirmation = true) + ?: promptForSecret("Enter password: ", requireConfirmation = true) ?: throw PrintMessage("Password can't be null") return PasswordAuth(username, effectivePassword) } override fun askForSecret(prompt: String, requireConfirmation: Boolean): String? = - prompt(prompt, hideInput = true, requireConfirmation = requireConfirmation) + promptForSecret(prompt, requireConfirmation = requireConfirmation) private suspend fun resolveParent( confluenceClient: ConfluenceClient, diff --git a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/ConvertTest.kt b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/ConvertTest.kt index f8d6eac8..f61b3930 100644 --- a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/ConvertTest.kt +++ b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/ConvertTest.kt @@ -3,7 +3,7 @@ package com.github.zeldigas.text2confl.cli import assertk.assertThat import assertk.assertions.exists import assertk.assertions.isFalse -import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.context import com.github.zeldigas.text2confl.convert.Attachment import com.github.zeldigas.text2confl.convert.Converter import com.github.zeldigas.text2confl.convert.Page @@ -23,14 +23,13 @@ import kotlin.io.path.div import kotlin.io.path.exists @ExtendWith(MockKExtension::class) -class ConvertTest ( +class ConvertTest( @MockK private val serviceProvider: ServiceProvider, @MockK private val converter: Converter, @MockK private val contentValidator: ContentValidator ) { private val command = Convert() - private val parentContext = Context.build(command) {} @BeforeEach internal fun setUp() { @@ -38,7 +37,9 @@ class ConvertTest ( every { serviceProvider.createContentValidator() } returns contentValidator every { contentValidator.validate(any()) } just Runs - parentContext.obj = serviceProvider + command.context { + obj = serviceProvider + } } @Test @@ -51,8 +52,7 @@ class ConvertTest ( listOf( "--docs", tempDir.toString(), "--out", outDir.toString() - ), - parentContext + ) ) assertThat(outDir / "a.html").exists() @@ -71,8 +71,7 @@ class ConvertTest ( "--copy-attachments", "--docs", tempDir.toString(), "--out", outDir.toString() - ), - parentContext + ) ) assertThat(outDir / "a.html").exists() @@ -92,8 +91,7 @@ class ConvertTest ( "--use-title", "--docs", tempDir.toString(), "--out", outDir.toString() - ), - parentContext + ) ) assertThat(outDir / "Special_name_ with _.html").exists() diff --git a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt index 7f28bec3..c94a58ef 100644 --- a/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt +++ b/cli/src/test/kotlin/com/github/zeldigas/text2confl/cli/UploadTest.kt @@ -8,8 +8,8 @@ import assertk.assertions.isTrue import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.module.kotlin.registerKotlinModule -import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.PrintMessage +import com.github.ajalt.clikt.core.context import com.github.zeldigas.confclient.ConfluenceClient import com.github.zeldigas.confclient.ConfluenceClientConfig import com.github.zeldigas.confclient.PasswordAuth @@ -44,7 +44,6 @@ internal class UploadTest( @MockK private val converter: Converter ) { private val command = Upload() - private val parentContext = Context.build(command) {} @BeforeEach internal fun setUp() { @@ -52,7 +51,10 @@ internal class UploadTest( every { serviceProvider.createConfluenceClient(any(), any()) } returns confluenceClient every { serviceProvider.createUploader(confluenceClient, any(), any()) } returns contentUploader every { serviceProvider.createContentValidator() } returns contentValidator - parentContext.obj = serviceProvider + + command.context { + obj = serviceProvider + } } @Test @@ -72,8 +74,7 @@ internal class UploadTest( "--remove-orphans", "all", "--tenant", "test", "--docs", tempDir.toString() - ), - parentContext + ) ) verify { @@ -118,8 +119,7 @@ internal class UploadTest( "--message", "custom upload message", "--docs", tempDir.toString(), "--dry" - ), - parentContext + ) ) verify { @@ -163,11 +163,10 @@ internal class UploadTest( "--space", "TR", "--confluence-url", "https://wiki.example.org", "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class).all { - transform { it.error }.isTrue() + transform { it.printError }.isTrue() hasMessage("Either access token or username/password should be specified") } } @@ -178,11 +177,10 @@ internal class UploadTest( command.parse( listOf( "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class).all { - transform { it.error }.isTrue() + transform { it.printError }.isTrue() hasMessage("Space is not specified. Use `--space` option or `space` in config file") } } @@ -194,11 +192,10 @@ internal class UploadTest( listOf( "--space", "TR", "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class).all { - transform { it.error }.isTrue() + transform { it.printError }.isTrue() hasMessage("Confluence url is not specified. Use `--confluence-url` option or `server` in config file") } } @@ -217,8 +214,7 @@ internal class UploadTest( "--access-token", "test", "--parent", "Test page", "--docs", tempDir.toString() - ), - parentContext + ) ) coVerify { @@ -239,8 +235,7 @@ internal class UploadTest( "--space", "TR", "--access-token", "test", "--docs", tempDir.toString() - ), - parentContext + ) ) coVerify { @@ -260,8 +255,7 @@ internal class UploadTest( "--space", "TR", "--access-token", "test", "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class).hasMessage("File does not exist: $tempDir") } @@ -281,8 +275,7 @@ internal class UploadTest( "--space", "TR", "--access-token", "test", "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class) .hasMessage("Failed to convert $tempDir: Conversion error message (cause: java.lang.RuntimeException: cause)") @@ -306,8 +299,7 @@ internal class UploadTest( "--space", "TR", "--access-token", "test", "--docs", tempDir.toString() - ), - parentContext + ) ) }.isInstanceOf(PrintMessage::class) .hasMessage("Some pages content is invalid:\n1. error message1\n2. error message2") diff --git a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt index 4c55463e..26f23afd 100644 --- a/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt +++ b/confluence-client/src/main/kotlin/com/github/zeldigas/confclient/ConfluenceClientImpl.kt @@ -209,7 +209,7 @@ class ConfluenceClientImpl( val result = mutableListOf() var start = 0 var limit = PAGE_SIZE - var completed = false + var completed: Boolean do { val page = httpClient.get("$apiBase/content/$pageId/child/page") { addExpansions(expansions ?: emptyList()) diff --git a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/AttachmentCollector.kt b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/AttachmentCollector.kt index c9801cf1..a88681b9 100644 --- a/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/AttachmentCollector.kt +++ b/convert/src/main/kotlin/com/github/zeldigas/text2confl/convert/markdown/AttachmentCollector.kt @@ -33,7 +33,7 @@ class AttachmentCollector( VisitHandler(LinkRef::class.java) { tryCollect(it, ast as Document) }, VisitHandler(Image::class.java) { tryCollect(it) }, VisitHandler(ImageRef::class.java) { tryCollect(it, ast as Document) }, - VisitHandler(Reference::class.java) { tryCollect(it, ast as Document) } + VisitHandler(Reference::class.java) { tryCollect(it) } )).visit(ast) } @@ -63,7 +63,7 @@ class AttachmentCollector( addFileIfExists(referenceNode.url.unescape(), referenceNode.reference.toString()) } - private fun tryCollect(reference: Reference, ast: Document) { + private fun tryCollect(reference: Reference) { addFileIfExists(reference.url.unescape(), reference.reference.toString()) }