All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rogach.scallop.ScallopHelpFormatter.scala Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version
package org.rogach.scallop

/** Used by ScallopConf.helpFormatter to create help output. Users of the library
  * can override chosen (or all) methods to tweak help output to their requirements.
  */
class ScallopHelpFormatter {

  def formatHelp(s: Scallop, subcommandPrefix: String): String = {
    val formattedHelp =
      getOptionsHelp(s) +
      getTrailingArgsHelp(s) +
      getSubcommandsHelp(s, subcommandPrefix)

    // remove trailing whitespace
    formattedHelp.split("\n")
    .map(_.reverse.dropWhile(Character.isWhitespace).reverse)
    .mkString("\n")
  }

  protected def getOptionsHelp(s: Scallop): String = {
    Formatter.format(
      getOptionLines(s).flatMap {
        case Left(s) => List(Left(s))
        case Right(o) => o.helpInfo(s.getOptionShortNames(o)).map(Right(_))
      },
      s.helpWidth,
      needToAppendDefaultToDescription(s)
    )
  }

  protected def getOptionLines(s: Scallop): List[Either[String, CliOption]] = {
    joinWithEmptyLineSeparator(List(
      getMainOptionLines(s),
      getOptionGroupsLines(s),
      getNormalOptionLines(s) ++ getHelpLine(s) ++ getVersionLine(s)
    ))
  }

  protected def getMainOptionLines(s: Scallop): List[Either[String, CliOption]] = {
    s.mainOptions.map(Right(_))
  }

  protected def getOptionGroupsLines(s: Scallop): List[Either[String, CliOption]] = {
    joinWithEmptyLineSeparator(s.optionGroups.map { case (header, options) =>
      List(header).filter(_.nonEmpty).map(h => Left(" " + h)) ++ options.map(Right(_))
    })
  }

  protected def getNormalOptionLines(s: Scallop): List[Either[String, CliOption]] = {
    val optionsInGroups: Set[CliOption] = s.mainOptions.toSet ++ s.optionGroups.flatMap(_._2)

    s.opts
    .filter(!_.isPositional)
    .filter(!_.hidden)
    .filter(o => !optionsInGroups.contains(o))
    .filter(o => o.name != "help" && o.name != "version")
    .sortBy(_.name.toLowerCase)
    .map(o => Right(o))
  }

  protected def getHelpLine(s: Scallop): List[Either[String, CliOption]] = {
    val helpOption =
      s.opts.find(_.name == "help")
      .getOrElse(s.getHelpOption)
    if (helpOption.hidden) Nil
    else List(Right(helpOption))
  }

  protected def getVersionLine(s: Scallop): List[Either[String, CliOption]] = {
    val versionOption =
      s.opts.find(_.name == "version")
      .orElse(s.vers.flatMap(_ => s.getVersionOption))
      .filterNot(_.hidden)
    versionOption match {
      case None => Nil
      case Some(o) => List(Right(o))
    }
  }

  protected def getTrailingArgsHelp(s: Scallop): String = {
    val trailOpts = s.opts.filter(_.isPositional).filter(!_.hidden)
    if (trailOpts.isEmpty) {
      ""
    } else {
      val trailHelp = Formatter.format(
        trailOpts.flatMap(_.helpInfo(Nil)).map(Right(_)),
        s.helpWidth,
        needToAppendDefaultToDescription(s)
      )
      "\n\n" + getTrailingArgsSectionName + "\n" + trailHelp
    }
  }

  protected def getTrailingArgsSectionName = " trailing arguments:"

  protected def getSubcommandsHelp(s: Scallop, subcommandPrefix: String): String = {
    if (s.subbuilders.isEmpty) {
      ""
    } else {
      if (s.shortSubcommandsHelp) {
        getShortSubcommandsHelp(s)
      } else {
        getLongSubcommandsHelp(s, subcommandPrefix)
      }
    }
  }

  protected def getShortSubcommandsHelp(s: Scallop): String = {
    val maxCommandLength = s.subbuilders.map(_._1.size).max

    "\n\n" + getSubcommandsSectionName + "\n" +
    s.subbuilders.map { case (name, option) =>
      "  " + name.padTo(maxCommandLength, ' ') + "   " + option.descr
    }.mkString("\n")
  }

  protected def getSubcommandsSectionName = "Subcommands:"

  protected def getLongSubcommandsHelp(s: Scallop, subcommandPrefix: String): String = {
    "\n\n" +
    getSubcommandsWithNames(s)
    .map { case (sub, names) =>
      getSubcommandHelp(sub, subcommandPrefix, names)
    }.mkString("\n\n")
  }

  protected def getSubcommandsWithNames(s: Scallop): List[(Scallop, List[String])] = {
    s.subbuilders
    .groupBy(_._2)
    .map { case (subBuilder, names) =>
      (subBuilder, names.map(_._1))
    }
    .toList
    // retain original subcommand ordering
    .sortBy { case (subBuilder, names) => s.subbuilders.indexWhere(_._2 == subBuilder) }
  }

  protected def getSubcommandHelp(sub: Scallop, namePrefix: String, names: List[String]): String = {
    List(
      getSubcommandHeader(sub, namePrefix, names),
      sub.bann.getOrElse(""),
      getSubcommandOptionsHelp(sub, namePrefix + names.head + " "),
      sub.foot.getOrElse("")
    ).filter(_.nonEmpty).mkString("\n")
  }

  protected def getSubcommandHeader(sub: Scallop, namePrefix: String, names: List[String]): String = {
    val name =
      if (names.size == 1) names.head
      else names.head + " (alias: " + names.tail.mkString(", ") + ")"
    val description = if (sub.descr.nonEmpty) " - " + sub.descr else ""
    getSubcommandHeaderPrefix + namePrefix + name + description
  }

  protected def getSubcommandHeaderPrefix = "Subcommand: "

  protected def getSubcommandOptionsHelp(sub: Scallop, namePrefix: String): String = {
    this.formatHelp(sub, namePrefix).split("\n")
    .filter(!_.trim.startsWith("--version"))
    .mkString("\n")
  }

  protected def needToAppendDefaultToDescription(s: Scallop): Boolean =
    s.appendDefaultToDescription || s.parent.exists(needToAppendDefaultToDescription)

  def getChoiceHelpText(description: String, choices: Seq[String]): String =
    Seq(description, "Choices:", choices.mkString(", ")).filter(_.nonEmpty).mkString(" ")


  private def joinWithEmptyLineSeparator(optionLists: List[List[Either[String, CliOption]]]): List[Either[String, CliOption]] = {
    optionLists match {
      case first :: second :: rest =>
        if (first.nonEmpty && second.nonEmpty) {
          joinWithEmptyLineSeparator((first ::: List(Left("")) ::: second) :: rest)
        } else {
          joinWithEmptyLineSeparator((first ::: second) :: rest)
        }
      case _ =>
        optionLists.flatten
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy