From 1907b94f7e6a38fc4a537b01ca795c46b1843c18 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sun, 22 Sep 2019 14:25:43 -0400 Subject: Add usage information --- commando/src/Command.scala | 71 ++++++++++++++++++++++++++++++++++++++++++++-- commando/src/Main.scala | 11 ++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/commando/src/Command.scala b/commando/src/Command.scala index eaf7804..0f65afb 100644 --- a/commando/src/Command.scala +++ b/commando/src/Command.scala @@ -5,7 +5,7 @@ object Command { case class ParseError(message: String) extends RuntimeException(message) } -class Command(val name: String) { +class Command(val name: String, val description: String = "") { import Command._ import collection.mutable @@ -15,7 +15,8 @@ class Command(val name: String) { var argName: String, var acceptsArg: Boolean, var requiresArg: Boolean, - var action: Option[String] => Unit + var action: Option[String] => Unit, + var info: String ) private case class PositionalParameter( @@ -30,6 +31,7 @@ class Command(val name: String) { class NamedBuilder(param: NamedParameter) { def action(fct: () => Unit) = { param.action = opt => fct(); this } + def info(text: String) = { param.info = text; this } def arg(name: String) = { param.argName = name; param.acceptsArg = true; param.requiresArg = true; @@ -45,9 +47,11 @@ class Command(val name: String) { def action(fct: String => Unit) = { param.action = opt => fct(opt.get); this } + def info(text: String) = { param.info = text; this } } class NamedOptArgBuilder(param: NamedParameter) { def action(fct: Option[String] => Unit) = { param.action = fct; this } + def info(text: String) = { param.info = text; this } } class PositionalBuilder(param: PositionalParameter) { @@ -59,7 +63,7 @@ class Command(val name: String) { def named(name: String, short: Char = 0): NamedBuilder = { val shortName = if (short == 0) None else Some(short) - val param = NamedParameter(name, shortName, "", false, false, _ => ()) + val param = NamedParameter(name, shortName, "", false, false, _ => (), "") namedParams += param new NamedBuilder(param) } @@ -240,4 +244,65 @@ class Command(val name: String) { |""".stripMargin } + def usage(): String = { + val named = namedParams.result() + val positional = posParams.result() + + val b = new mutable.StringBuilder + + // headline + b.append("Usage: ") + b.append(name); + if (!named.isEmpty) { + b.append(" [options]") + } + for (pos <- positional) { + b.append(" ") + if (pos.optional) { + b.append("["); b.append(pos.name); b.append("]") + } else { + b.append(pos.name) + } + if (pos.repeated) { + b.append("...") + } + } + + // command description + if (!description.isEmpty) { + b.append("\n\n") + b.append(description) + } + + // parameters + if (!named.isEmpty) { + b.append("\n\nOptions:\n") + + named.sortBy(_.name).foreach { param => + val short = param.short.map(c => s" -$c, ").getOrElse(" ") + val long = if (param.requiresArg) { + s"--${param.name}=${param.argName}" + } else if (param.acceptsArg) { + s"--${param.name}[=${param.argName}]" + } else { + s"--${param.name}" + } + + b.append("\n") + if (long.length <= 22) { + b.append(short) + b.append(f"$long%-22s ") + b.append(param.info) + } else { + b.append(short) + b.append(f"$long\n") + b.append(" " * 30) + b.append(param.info) + } + + } + } + b.result() + } + } diff --git a/commando/src/Main.scala b/commando/src/Main.scala index 3270019..043a9f2 100644 --- a/commando/src/Main.scala +++ b/commando/src/Main.scala @@ -2,11 +2,13 @@ package example object Main extends App { - val cmd = new commando.Command("xorc") { + val cmd = new commando.Command("xorc", "") { + val version = named("version", 'V') .action(() => println("version 1")) named("verbose", 'v') + .info("Set verbosity level. This option may be repeated.") .optionalArg("level") .action(level => println(s"level $level")) @@ -19,8 +21,15 @@ object Main extends App { .repeat() val comp = named("completion") + .info( + s"Print bash completion. Add ${name} --completions to your bashrc to get completions." + ) .action(() => println(completion())) + named("help", 'h') + .info("print a help message") + .action(() => println(usage())) + } cmd.parse(args) match { case None => -- cgit v1.2.3