Skip to content

Commit

Permalink
Add support for HelpMessage annotation on case class (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
regadas authored Mar 16, 2020
1 parent 28899aa commit 85e378a
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package caseapp.core.help

import caseapp.core.Arg
import dataclass.data
import caseapp.HelpMessage

@data class CommandHelp(
args: Seq[Arg],
argsNameOption: Option[String]
argsNameOption: Option[String],
helpMessage: Option[HelpMessage]
) {

def usageMessage(progName: String, commandName: Seq[String]): String =
Expand All @@ -17,6 +19,8 @@ import dataclass.data
def helpMessage(progName: String, commandName: Seq[String]): String = {
val b = new StringBuilder
b ++= s"Command: ${commandName.mkString(" ")}${Help.NL}"
for (m <- helpMessage)
b ++= m.message
b ++= usageMessage(progName, commandName)
b ++= Help.NL
b ++= optionsMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import shapeless.{:+:, CNil, Coproduct, LabelledGeneric, Strict, Witness}
import shapeless.labelled.FieldType

import scala.language.implicitConversions
import caseapp.HelpMessage


@data class CommandsHelp[T](
Expand All @@ -32,7 +33,8 @@ object CommandsHelp {
commandName: AnnotationOption[CommandName, H],
parser: Strict[Parser[H]],
argsName: AnnotationOption[ArgsName, H],
tail: CommandsHelp[T]
tail: CommandsHelp[T],
helpMessage: AnnotationOption[HelpMessage, H]
): CommandsHelp[FieldType[K, H] :+: T] = {
// FIXME Duplicated in CommandParser.ccons
val name = commandName().map(_.commandName).getOrElse {
Expand All @@ -43,7 +45,8 @@ object CommandsHelp {

CommandsHelp((Seq(name) -> CommandHelp(
parser.value.args,
argsName().map(_.argsName)
argsName().map(_.argsName),
helpMessage()
)) +: tail.messages)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ package caseapp.core.help
import caseapp.core.Arg
import caseapp.core.app.CaseApp
import shapeless._
import caseapp.HelpMessage

final class CommandsHelpOps[C <: Coproduct](val commandsHelp: CommandsHelp[C]) extends AnyVal {

def add[H](
name: String,
args: Seq[Arg],
argsNameOption: Option[String]
argsNameOption: Option[String],
helpMessage: Option[HelpMessage]
): CommandsHelp[H :+: C] =
CommandsHelp(
commandsHelp.messages :+ (Seq(name) -> CommandHelp(args, argsNameOption))
commandsHelp.messages :+ (Seq(name) -> CommandHelp(args, argsNameOption, helpMessage))
)

def add[H](
command: CaseApp[H],
name: String = ""
name: String = "",
helpMessage: Option[HelpMessage] = None
): CommandsHelp[H :+: C] =
add(
if (name.isEmpty) command.messages.progName else name,
command.parser.args,
command.messages.argsNameOption
command.messages.argsNameOption,
helpMessage
)

def reverse[R <: Coproduct](implicit rev: ops.coproduct.Reverse.Aux[C, R]): CommandsHelp[R] =
Expand Down
12 changes: 9 additions & 3 deletions core/shared/src/main/scala/caseapp/core/help/Help.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import caseapp.core.util.NameOps.toNameOps
import dataclass.data
import shapeless.Typeable
import caseapp.core.util.Formatter
import caseapp.HelpMessage

/**
* Provides usage and help messages related to `T`
Expand All @@ -20,7 +21,8 @@ import caseapp.core.util.Formatter
progName: String,
argsNameOption: Option[String],
optionsDesc: String = Help.DefaultOptionsDesc,
nameFormatter: Formatter[Name] = Help.DefaultNameFormatter
nameFormatter: Formatter[Name] = Help.DefaultNameFormatter,
helpMessage: Option[HelpMessage] = Help.DefaultHelpMessage
) {

/** One-line usage message for `T` */
Expand All @@ -47,6 +49,7 @@ import caseapp.core.util.Formatter
if (appVersion.nonEmpty)
b ++= s" $appVersion"
b ++= Help.NL
helpMessage.foreach(msg => b ++= s"${msg.message}${Help.NL}")
b ++= usage
b ++= Help.NL
b ++= options
Expand Down Expand Up @@ -94,6 +97,7 @@ import caseapp.core.util.Formatter
object Help {
val DefaultOptionsDesc = "[options]"
val DefaultNameFormatter = Formatter.DefaultNameFormatter
val DefaultHelpMessage = Option.empty[HelpMessage]

/** Look for an implicit `Help[T]` */
def apply[T](implicit help: Help[T]): Help[T] = help
Expand Down Expand Up @@ -136,7 +140,8 @@ object Help {
appName: AnnotationOption[AppName, T],
appVersion: AnnotationOption[AppVersion, T],
progName: AnnotationOption[ProgName, T],
argsName: AnnotationOption[ArgsName, T]
argsName: AnnotationOption[ArgsName, T],
helpMessage: AnnotationOption[HelpMessage, T],
): Help[T] = {

val appName0 = appName().fold(typeable.describe.stripSuffix("Options"))(_.appName)
Expand All @@ -148,7 +153,8 @@ object Help {
progName().fold(CaseUtil.pascalCaseSplit(appName0.toList).map(_.toLowerCase).mkString("-"))(_.progName),
argsName().map(_.argsName),
Help.DefaultOptionsDesc,
parser.defaultNameFormatter
parser.defaultNameFormatter,
helpMessage()
)
}

Expand Down
15 changes: 15 additions & 0 deletions tests/shared/src/test/scala/caseapp/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ object Definitions {
bar: Int
)

@HelpMessage("Example help message")
final case class ExampleWithHelpMessage(
foo: String,
bar: Int
)

sealed trait Command

case class First(
Expand All @@ -124,6 +130,15 @@ object Definitions {
baz: Int = 0
) extends Command

@HelpMessage("Third help message")
case class Third(
third: Int = 0
) extends Command

object CommandTest extends CommandApp[Command] {
def run(options: Command, remainingArgs: RemainingArgs): Unit = {}
}

case class Default0(bah: Double = 0.0)

object Duplicates {
Expand Down
25 changes: 25 additions & 0 deletions tests/shared/src/test/scala/caseapp/HelpTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ object HelpTests extends TestSuite {
checkLines(message, expectedMessage)
}

"generate a help message" - {

val message = CaseApp.helpMessage[ExampleWithHelpMessage]

val expectedMessage =
"""ExampleWithHelpMessage
|Example help message
|Usage: example-with-help-message [options]
| --foo <string>
| --bar <int>""".stripMargin

checkLines(message, expectedMessage)
}

"mark optional options with ? in help messages" - {
val message = CaseApp.helpMessage[OptBool]

Expand Down Expand Up @@ -150,6 +164,17 @@ object HelpTests extends TestSuite {
checkLines(message, expectedMessage)
}

"generate help message for commands" - {
val msg = CommandTest.commandsMessages
.messagesMap(List("third"))
.helpMessage
.map(_.message)
.getOrElse("")
val expected = "Third help message"

assert(msg == expected)
}

}

}
1 change: 1 addition & 0 deletions tests/shared/src/test/scala/caseapp/demo/Demo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final case class First(
) extends DemoCommand

@CommandName("second")
@HelpMessage("second cmd help")
final case class Secondd(
extra: List[Int],
@ExtraName("S")
Expand Down

0 comments on commit 85e378a

Please sign in to comment.