diff --git a/annotations/shared/src/main/scala/caseapp/Annotations.scala b/annotations/shared/src/main/scala/caseapp/Annotations.scala index f979823f..3235e029 100644 --- a/annotations/shared/src/main/scala/caseapp/Annotations.scala +++ b/annotations/shared/src/main/scala/caseapp/Annotations.scala @@ -17,8 +17,10 @@ object ValueDescription { } /** Help message for the annotated argument + * @messageMd + * not used by case-app itself, only there as a convenience for case-app users */ -final case class HelpMessage(message: String) extends StaticAnnotation +final case class HelpMessage(message: String, messageMd: String = "") extends StaticAnnotation /** Name for the annotated case class of arguments E.g. MyApp */ diff --git a/core/shared/src/main/scala/caseapp/core/complete/Bash.scala b/core/shared/src/main/scala/caseapp/core/complete/Bash.scala index e7a0f397..ea223ccf 100644 --- a/core/shared/src/main/scala/caseapp/core/complete/Bash.scala +++ b/core/shared/src/main/scala/caseapp/core/complete/Bash.scala @@ -1,5 +1,7 @@ package caseapp.core.complete +import scala.util.Properties + object Bash { val shellName: String = @@ -19,7 +21,7 @@ object Bash { } private def escape(s: String): String = - s.replace("\"", "\\\"") + s.replace("\"", "\\\"").replace("`", "\\`").split("\\r?\\n").headOption.getOrElse("") def print(items: Seq[CompletionItem]): String = { val newLine = System.lineSeparator() val b = new StringBuilder diff --git a/core/shared/src/main/scala/caseapp/core/complete/Zsh.scala b/core/shared/src/main/scala/caseapp/core/complete/Zsh.scala index 3a8db7e1..d37040d2 100644 --- a/core/shared/src/main/scala/caseapp/core/complete/Zsh.scala +++ b/core/shared/src/main/scala/caseapp/core/complete/Zsh.scala @@ -7,6 +7,7 @@ import java.security.MessageDigest import dataclass.data import scala.collection.mutable +import scala.util.Properties object Zsh { @@ -34,14 +35,15 @@ object Zsh { else res } - + private def escape(s: String): String = + s.replace("'", "\\'").replace("`", "\\`").split("\\r?\\n").headOption.getOrElse("") private def defs(item: CompletionItem): Seq[String] = { val (options, arguments) = item.values.partition(_.startsWith("-")) val optionsOutput = if (options.isEmpty) Nil else { val escapedOptions = options - val desc = item.description.map(":" + _.replace("'", "\\'")).getOrElse("") + val desc = item.description.map(desc => ":" + escape(desc)).getOrElse("") options.map { opt => "\"" + opt + desc + "\"" } @@ -49,7 +51,7 @@ object Zsh { val argumentsOutput = if (arguments.isEmpty) Nil else { - val desc = item.description.map(":" + _.replace("'", "\\'")).getOrElse("") + val desc = item.description.map(desc => ":" + escape(desc)).getOrElse("") arguments.map("'" + _.replace(":", "\\:") + desc + "'") } optionsOutput ++ argumentsOutput diff --git a/tests/shared/src/test/scala/caseapp/CompletionDefinitions.scala b/tests/shared/src/test/scala/caseapp/CompletionDefinitions.scala index d599ecd2..59180e7c 100644 --- a/tests/shared/src/test/scala/caseapp/CompletionDefinitions.scala +++ b/tests/shared/src/test/scala/caseapp/CompletionDefinitions.scala @@ -59,18 +59,30 @@ object CompletionDefinitions { @Name("g") @HelpMessage("A pattern") glob: String = "", @Name("d") count: Int = 0 ) + case class BackTickOptions( + @HelpMessage( + """A pattern with backtick `--` + |with multiline + |""".stripMargin + ) backtick: String = "", + @Name("d") count: Int = 0 + ) object First extends Command[FirstOptions] { def run(options: FirstOptions, args: RemainingArgs): Unit = ??? } object Second extends Command[SecondOptions] { def run(options: SecondOptions, args: RemainingArgs): Unit = ??? } + object BackTick extends Command[BackTickOptions] { + def run(options: BackTickOptions, args: RemainingArgs): Unit = ??? + } object Prog extends CommandsEntryPoint { def progName = "prog" def commands = Seq( First, - Second + Second, + BackTick ) } } diff --git a/tests/shared/src/test/scala/caseapp/CompletionTests.scala b/tests/shared/src/test/scala/caseapp/CompletionTests.scala index 077a9651..ac843153 100644 --- a/tests/shared/src/test/scala/caseapp/CompletionTests.scala +++ b/tests/shared/src/test/scala/caseapp/CompletionTests.scala @@ -1,6 +1,6 @@ package caseapp -import caseapp.core.complete.CompletionItem +import caseapp.core.complete.{Bash, CompletionItem, Zsh} import utest._ object CompletionTests extends TestSuite { @@ -110,6 +110,7 @@ object CompletionTests extends TestSuite { test { val res = Prog.complete(Seq(""), 0) val expected = List( + CompletionItem("back-tick", None, Nil), CompletionItem("first", None, Nil), CompletionItem("second", None, Nil) ) @@ -140,6 +141,33 @@ object CompletionTests extends TestSuite { ) assert(res == expected) } + + test("bash") { + val res = Prog.complete(Seq("back-tick", "-"), 1) + val expected = List( + CompletionItem("--backtick", Some("A pattern with backtick `--`"), Nil), + CompletionItem("--count", None, List("-d")) + ) + assert(res == expected) + + val compRely = Bash.print(res) + val expectedCompRely = """"--backtick -- A pattern with backtick \`--\`"""".stripMargin + + assert(compRely.contains(expectedCompRely)) + } + test("zsh") { + val res = Prog.complete(Seq("back-tick", "-"), 1) + val expected = List( + CompletionItem("--backtick", Some("A pattern with backtick `--`"), Nil), + CompletionItem("--count", None, List("-d")) + ) + assert(res == expected) + + val compRely = Zsh.print(res) + val expectedCompRely = """"--backtick:A pattern with backtick \`--\`"""".stripMargin + + assert(compRely.contains(expectedCompRely)) + } } test("commands with default") {