
org.rogach.scallop.ScallopConfBase.scala Maven / Gradle / Ivy
package org.rogach.scallop
import java.io.File
import java.nio.file.{Files, Path}
import scala.collection.{Seq => CSeq}
import scala.collection.mutable.ArrayBuffer
import org.rogach.scallop.exceptions._
/** Base class for CLI subcommands. */
class Subcommand(commandNameAndAliases: String*) extends ScallopConf(Nil, commandNameAndAliases) {
/** Short description for this subcommand. Used if parent command has shortSubcommandsHelp enabled. */
def descr(d: String): Unit = {
editBuilder(_.copy(descr = d))
}
}
/** Contains non-platform-specific functionality of ScallopConf. */
abstract class ScallopConfBase(
val args: CSeq[String] = Nil,
protected val commandNameAndAliases: Seq[String] = Nil
) extends ScallopConfValidations {
/** Pointer to parent ScallopConf */
protected var parentConfig: ScallopConfBase = this
/** true if this config does not represent a subcommand */
protected var isRootConfig = true
private def rootConfig: ScallopConfBase = {
var conf = this
while (!conf.isRootConfig) {
conf = conf.parentConfig
}
conf
}
/** List of sub-configs of this config. */
protected var subconfigs: Seq[ScallopConfBase] = Nil
/** Add subcommand to this config */
def addSubcommand(conf: Subcommand): Unit = {
subconfigs :+= conf
conf.parentConfig = this
conf.isRootConfig = false
conf.verifyConf()
conf.verified = true
editBuilder(_.addSubBuilder(conf.commandNameAndAliases, conf.builder))
}
/** Internal immutable builder for options setup. */
var builder = Scallop(args)
private[scallop] def editBuilder(fn: Scallop => Scallop): Unit = {
builder = fn(builder)
}
// machinery to support option name guessing
protected var _guessOptionName: Boolean = true
protected def optionNameGuessingSupported: Boolean
protected def performOptionNameGuessing(): Unit
/** If set to true, scallop would append auto-generated text about default option value
* to option descriptions. */
def appendDefaultToDescription = builder.appendDefaultToDescription
/** If set to true, scallop would append auto-generated text about default option value
* to option descriptions. */
def appendDefaultToDescription_=(v: Boolean): Unit = {
editBuilder(_.copy(appendDefaultToDescription = v))
}
/** Get current custom help formatter. */
def helpFormatter = builder.helpFormatter
/** Set custom help formatter. */
def helpFormatter_=(formatter: ScallopHelpFormatter) = {
editBuilder(_.copy(helpFormatter = formatter))
}
/** If set to true, then do not generate short names for subsequently defined options by default.
* Only applied if a subsequent option definition does not explicitly provide its noshort-parameter.
*/
def noshort = builder.noshort
/** If set to true, then do not generate short names for subsequently defined options by default.
* Only applied if a subsequent option definition does not explicitly provide its noshort-parameter.
*/
def noshort_=(v: Boolean): Unit = {
editBuilder(_.copy(noshort = v))
}
private[this] var gen = 0
private[this] def genName() = { gen += 1; Util.format("\t%d", gen) }
/** Retrieves the choosen subcommand. */
def subcommand: Option[ScallopConfBase] = {
assertVerified()
builder.getSubcommandName.map(n => subconfigs.find(_.commandNameAndAliases.contains(n)).get)
}
/** Retrieves the list of the chosen nested subcommands. */
def subcommands: List[ScallopConfBase] = {
assertVerified()
var config = this
var configs = List[ScallopConfBase]()
builder.getSubcommandNames.foreach { bn =>
config = config.subconfigs.find(_.commandNameAndAliases.contains(bn)).get
configs :+= config
}
configs
}
/** Get current prefix to command name (consists of parent builder names, separated by null char) */
private def getPrefix = {
var prefix = ""
var conf = this
while (!conf.isRootConfig) {
prefix = conf.commandNameAndAliases.head + "\u0000" + prefix
conf = conf.parentConfig
}
prefix
}
private def getPrefixedName(name: String) = getPrefix + name
private[scallop] var verified = false
/** Add a new simple option definition to this config.
*
* @param name Name for new option, used as long option name in parsing, and for option identification.
* @param short By default, the first character of option name is used for short option name. You can override it by specifying the required character (`short = 'c'`).
* @param descr Description for the option. Will be printed in help message, carefully formatted to the output width (80 characters by default).
* @param default Default value to use if option is not found in input arguments (if you provide this, you can omit the type on method).
* @param validate The function that validates the parsed value.
* @param required Is this option required? Defaults to false.
* @param argName The name for this option argument, as it will appear in help. Defaults to "arg".
* @param hidden Hides description of this option from help (this can be useful for debugging options).
* @param noshort If set to true, then this option does not have any short name.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def opt[A](
name: String = null,
short: Char = '\u0000',
descr: String = "",
default: => Option[A] = None,
validate: A => Boolean = (_:A) => true,
required: Boolean = false,
argName: String = "arg",
hidden: Boolean = false,
noshort: Boolean = builder.noshort,
group: ScallopOptionGroup = null
)(implicit conv:ValueConverter[A]): ScallopOption[A] = {
// guessing name, if needed
val resolvedName =
if (name == null)
if (_guessOptionName) {
genName() // generate unique name, that will be replaced during verification with guessed name
}
else throw new IllegalArgumentException("You should supply a name for your option!")
else name
if (resolvedName.head.isDigit) {
throw new IllegalOptionParameters(Util.format("First character of the option name must not be a digit: %s", resolvedName))
}
val defaultA =
if (conv == flagConverter)
{ () =>
if (default == Some(true)) Some(true)
else Some(false)
}
else () => default
val optionDescriptor = SimpleOption(
name = resolvedName,
short = if (short == '\u0000' || noshort) None else Some(short),
descr = descr,
required = required,
converter = conv,
default = defaultA,
validator = { (a:Any) => validate(a.asInstanceOf[A]) },
argName = argName,
hidden = hidden,
noshort = noshort
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new ScallopOption[A](() => resolvedName, Some(optionDescriptor)) {
override lazy val fn = { (name: String) =>
assertVerified()
rootConfig.builder.get(getPrefixedName(name)).asInstanceOf[Option[A]]
}
override lazy val supplied = { (name: String) =>
assertVerified()
rootConfig.builder.isSupplied(getPrefixedName(name))
}
}
}
/** Add a new choice option definition to this config.
*
* This option takes a single string argument and restricts values to a list of possible choices.
*
* @param choices List of possible argument values.
* @param name Name for new option, used as long option name in parsing, and for option identification.
* @param short Overload the char that will be used as short option name. Defaults to first character of the name.
* @param descr Description for this option, for help description.
* @param default Default value to use if option is not found in input arguments (if you provide this, you can omit the type on method).
* @param required Is this option required? Defaults to false.
* @param argName The name for this option argument, as it will appear in help. Defaults to "arg".
* @param hidden If set to true, then this option will be hidden from generated help output.
* @param noshort If set to true, then this option does not have any short name.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def choice(
choices: Seq[String],
name: String = null,
short: Char = '\u0000',
descr: String = "",
default: => Option[String] = None,
required: Boolean = false,
argName: String = "arg",
hidden: Boolean = false,
noshort: Boolean = noshort,
group: ScallopOptionGroup = null
): ScallopOption[String] = {
this.opt[String](
name = name,
short = short,
descr = this.helpFormatter.getChoiceHelpText(descr, choices),
default = default,
required = required,
argName = argName,
hidden = hidden,
noshort = noshort,
group = group
)(new ValueConverter[String] {
def parse(s: List[(String, List[String])]) = {
s match {
case (_, arg :: Nil) :: Nil =>
if (choices.contains(arg)) {
Right(Some(arg))
} else {
Left(s"Expected one of: ${choices.mkString(", ")}")
}
case Nil => Right(None)
case _ => Left("You should provide exactly one argument for this option")
}
}
val argType = ArgType.SINGLE
})
}
/** Add a new tally option definition to this config.
*
* Tally options count how many times the option was provided on the command line.
* E.g., `-vvv` will be countet as `3`.
*
* @param name Name for new option, used as long option name in parsing, and for option identification.
* @param short Overload the char that will be used as short option name. Defaults to first character of the name.
* @param descr Description for this option, for help description.
* @param hidden If set to true, then this option will be hidden from generated help output.
* @param noshort If set to true, then this option does not have any short name.
* @param group Option group to add this option to.
* @return ScallopOption, container for the parsed option value.
*/
def tally(
name: String = null,
short: Char = '\u0000',
descr: String = "",
hidden: Boolean = false,
noshort: Boolean = builder.noshort,
group: ScallopOptionGroup = null
): ScallopOption[Int] = {
// guessing name, if needed
val resolvedName =
if (name == null)
if (_guessOptionName) genName()
else throw new IllegalArgumentException("You should supply a name for your option!")
else name
val optionDescriptor = SimpleOption(
name = resolvedName,
short = if (short == '\u0000' || noshort) None else Some(short),
descr = descr,
required = false,
converter = tallyConverter,
default = () => Some(0),
validator = _ => true,
argName = "",
hidden = hidden,
noshort = noshort
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new ScallopOption[Int](() => resolvedName, Some(optionDescriptor)) {
override lazy val fn = { (name: String) =>
assertVerified()
rootConfig.builder.get(getPrefixedName(name)).asInstanceOf[Option[Int]]
}
override lazy val supplied = { (name: String) =>
assertVerified()
rootConfig.builder.isSupplied(getPrefixedName(name))
}
}
}
/** Add new property option definition to this config object.
*
* This option will parse arguments like `-Dkey=value` or `-D key1=value1 key2=value2`.
*
* @param name Character that will be used as prefix for property arguments.
* @param descr Description for this property option, for help description.
* @param keyName Name for 'key' part of this option arg name, as it will appear in help option definition. Defaults to "key".
* @param valueName Name for 'value' part of this option arg name, as it will appear in help option definition. Defaults to "value".
* @param hidden If set to true, then this option will be hidden from generated help output.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def props[A](
name: Char = 'D',
descr: String = "",
keyName: String = "key",
valueName: String = "value",
hidden: Boolean = false,
group: ScallopOptionGroup = null
)(implicit conv: ValueConverter[Map[String,A]]): LazyMap[String, A] = {
val optionDescriptor = PropertyOption(
name = name.toString,
short = name,
descr = descr,
converter = conv,
keyName = keyName,
valueName = valueName,
hidden = hidden
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new LazyMap({
assertVerified()
rootConfig.builder.apply(getPrefixedName(name.toString)).asInstanceOf[Map[String, A]]
}, Some(optionDescriptor))
}
/** Add new property option definition to this config object.
*
* This option will parse arguments like `--Props key1=value1 key2=value2`.
*
* @param name Name for new option, used as long option name in parsing, and for option identification.
* @param descr Description for this property option, for help description.
* @param keyName Name for 'key' part of this option arg name, as it will appear in help option definition. Defaults to "key".
* @param valueName Name for 'value' part of this option arg name, as it will appear in help option definition. Defaults to "value".
* @param hidden If set to true, then this option will be hidden from generated help output.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def propsLong[A](
name: String = "Props",
descr: String = "",
keyName: String = "key",
valueName: String = "value",
hidden: Boolean = false,
group: ScallopOptionGroup = null
)(implicit conv: ValueConverter[Map[String,A]]): Map[String, A] = {
val optionDescriptor = LongNamedPropertyOption(
name = name,
descr = descr,
converter = conv,
keyName = keyName,
valueName = valueName,
hidden = hidden
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new LazyMap({
assertVerified()
rootConfig.builder.apply(getPrefixedName(name)).asInstanceOf[Map[String, A]]
}, Some(optionDescriptor))
}
/** Add new trailing argument definition to this config.
*
* @param name Name for new definition, used for identification.
* @param descr Description for this option, for help text.
* @param validate The function that validates the parsed value.
* @param required Is this trailing argument required? Defaults to true.
* @param default If this argument is not required and not found in the argument list, use this value.
* @param hidden If set to true then this option will not be present in auto-generated help.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def trailArg[A](
name: String = null,
descr: String = "",
validate: A => Boolean = (_:A) => true,
required: Boolean = true,
default: => Option[A] = None,
hidden: Boolean = false,
group: ScallopOptionGroup = null
)(implicit conv:ValueConverter[A]): ScallopOption[A] = {
val resolvedName = if (name == null) genName() else name
val defaultA =
if (conv == flagConverter)
{ () =>
if (default == Some(true)) Some(true)
else Some(false)
}
else () => default
val optionDescriptor = TrailingArgsOption(
name = resolvedName,
required = required,
descr = descr,
converter = conv,
validator = { (a:Any) => validate(a.asInstanceOf[A]) },
default = defaultA,
hidden = hidden
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new ScallopOption[A](() => resolvedName, Some(optionDescriptor)) {
override lazy val fn = { (name: String) =>
assertVerified()
rootConfig.builder.get(getPrefixedName(name)).asInstanceOf[Option[A]]
}
override lazy val supplied = { (name: String) =>
assertVerified()
rootConfig.builder.isSupplied(getPrefixedName(name))
}
}
}
/** Add new number argument definition to this config and get a holder for it's value.
*
* Parses arguments like `-1` or `-3` (like GNU tail, for example).
*
* @param name Name for new definition, used for identification.
* @param required Is this trailing argument required? Defaults to true.
* @param descr Description for this option, for help text.
* @param default If this argument is not required and not found in the argument list, use this value.
* @param validate The function that validates the parsed value.
* @param hidden If set to true then this option will not be present in auto-generated help.
* @param group Option group to add this option to.
* @param conv The converter for this option. Usually found implicitly.
* @return ScallopOption, container for the parsed option value.
*/
def number(
name: String = null,
descr: String = "",
validate: Long => Boolean = (_:Long) => true,
required: Boolean = false,
default: => Option[Long] = None,
hidden: Boolean = false,
group: ScallopOptionGroup = null
)(implicit conv: ValueConverter[Long]): ScallopOption[Long] = {
val resolvedName =
if (name == null) {
if (_guessOptionName) genName()
else throw new IllegalArgumentException("You should supply a name for your number option!")
} else name
val optionDescriptor = NumberArgOption(
name = resolvedName,
required = required,
descr = descr,
converter = conv,
validator = { (a: Any) => validate(a.asInstanceOf[Long]) },
default = () => default,
hidden = hidden
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new ScallopOption[Long](() => resolvedName, Some(optionDescriptor)) {
override lazy val fn = { (name: String) =>
assertVerified()
rootConfig.builder.get(getPrefixedName(name)).asInstanceOf[Option[Long]]
}
override lazy val supplied = { (name: String) =>
assertVerified()
rootConfig.builder.isSupplied(getPrefixedName(name))
}
}
}
/** Add new toggle option definition to this config, and get a holder for it's value.
*
* Toggle options are just glorified flag options. For example, if you create a
* toggle option with name "verbose", it will be invocable in three ways -
* "--verbose", "--noverbose", "-v".
*
* @param name Name of this option
* @param default default value for this option
* @param short Overload the char that will be used as short option name. Defaults to first character of the name.
* @param noshort If set to true, then this option will not have any short name.
* @param prefix Prefix to name of the option, that will be used for "negative" version of the option.
* @param descrYes Description for positive variant of this option.
* @param descrNo Description for negative variant of this option.
* @param required Is this option required? Defaults to false.
* @param hidden If set to true, then this option will not be present in auto-generated help.
* @param group Option group to add this option to.
* @return ScallopOption, container for the parsed option value.
*/
def toggle(
name: String = null,
default: => Option[Boolean] = None,
short: Char = '\u0000',
noshort: Boolean = noshort,
prefix: String = "no",
descrYes: String = "",
descrNo: String = "",
required: Boolean = false,
hidden: Boolean = false,
group: ScallopOptionGroup = null
): ScallopOption[Boolean] = {
val resolvedName =
if (name == null) {
if (_guessOptionName) genName()
else throw new IllegalArgumentException("You should supply a name for your toggle!")
} else name
val optionDescriptor = ToggleOption(
resolvedName,
default = () => default,
short = if (short == '\u0000' || noshort) None else Some(short),
noshort = noshort,
prefix = prefix,
descrYes = descrYes,
descrNo = descrNo,
required = required,
hidden = hidden
)
if (group != null) {
group.options.append(optionDescriptor)
}
editBuilder(_.appendOption(optionDescriptor))
new ScallopOption[Boolean](() => resolvedName, Some(optionDescriptor)) {
override lazy val fn = { (name: String) =>
assertVerified()
rootConfig.builder.get(getPrefixedName(name)).asInstanceOf[Option[Boolean]]
}
override lazy val supplied = { (name: String) =>
assertVerified()
rootConfig.builder.isSupplied(getPrefixedName(name))
}
}
}
private var _mainOptions: () => Seq[CliOption] = () => Nil
/** Options, that are to be printed first in the help printout */
def mainOptions = _mainOptions()
/** Set options, that are to be printed first in the help printout */
@deprecated(
"Use option groups instead, for example see https://github.com/scallop/scallop/wiki/Help-information-printing#option-groups",
since = "Scallop 4.0.0"
)
def mainOptions_=(newMainOptions: => Seq[ScallopOption[_]]) = {
_mainOptions = () => {
newMainOptions.flatMap(_.cliOption)
}
}
private val optionGroups: ArrayBuffer[ScallopOptionGroup] = new ArrayBuffer()
/** Create and return a new option group */
def group(header: String = ""): ScallopOptionGroup = {
val newGroup = new ScallopOptionGroup(header)
optionGroups.append(newGroup)
newGroup
}
/** Verify that this config object is properly configured. */
private[scallop] def verifyBuilder(): Unit = {
try {
verified = true
builder.verify
runValidations()
} catch {
case e: Exception =>
onError(e)
}
}
private[scallop] def runValidations(): Unit = {
validations foreach { v =>
v() match {
case Right(_) =>
case Left(err) => throw new ValidationFailure(err)
}
}
for {
subBuilder <- builder.getSubbuilder
subConfig <- subconfigs.find(_.builder == subBuilder)
} {
subConfig.editBuilder(_.args(builder.getSubcommandArgs))
subConfig.runValidations()
}
}
/** This name would be included in output when reporting errors. */
var printedName = "scallop"
/** This function is called with the error message when ScallopException
* occurs. By default, this function prints message (prefixed by *printedName*) to stderr,
* coloring the output if possible, then calls `exitHandler(1)`.
*
* Update this variable with another function if you need to change that behavior.
*/
var errorMessageHandler: String => Unit = (_) => {}
/** This function is called with an exit code when Scallop thinks it's time to
* terminate. By default this calls sys.exit(exitCode).
*
* Update this variable with another function if you need to change that behavior.
*/
var exitHandler: Int => Unit = exitCode => Compat.exit(exitCode)
/** This function is called with a string when Scallop needs to output text to stdout.
* Update this variable if you need to redirect stdout output somewhere else.
*/
var stdoutPrintln: String => Unit = string => Console.out.println(string)
/** This function is called with a string when Scallop needs to output text to stderr.
* Update this variable if you need to redirect stderr output somewhere else.
*/
var stderrPrintln: String => Unit = string => Console.err.println(string)
/** This function is called in event of any exception
* in arguments parsing. By default, it catches only
* standard Scallop errors, letting all other pass through.
*/
protected def onError(e: Throwable): Unit = e match {
case r: ScallopResult if !throwError.value => r match {
case Help("") =>
stdoutPrintln(builder.getFullHelpString())
exitHandler(0)
case Help(subname) =>
stdoutPrintln(builder.findSubbuilder(subname).get.getFullHelpString())
exitHandler(0)
case Version =>
getVersionString().foreach(stdoutPrintln)
exitHandler(0)
case ScallopException(message) => errorMessageHandler(message)
// following should never match, but just in case
case other: exceptions.ScallopException => errorMessageHandler(other.getMessage)
}
case e => throw e
}
/** Checks that this Conf object is verified. If it is not, throws an exception. */
def assertVerified(): Unit = {
if (!verified) {
throw new IncompleteBuildException()
}
}
/** Adds a validation function to this configuration. This function will be run after all other verification steps.
* @param fn Validation function. In case of error, it should return Left with the error message.
*/
def addValidation(fn: => Either[String, Unit]): Unit = {
validations :+= (() => fn)
}
/** Add a check that at least one of the options in the list was supplied if `opt` was supplied.
* @param opt option, that depends on any of options in list
* @param list list of dependencies (at least one will need to be present)
*/
def dependsOnAny(opt: ScallopOption[_], list: List[ScallopOption[_]]) = addValidation {
if (opt.isSupplied && !list.exists(_.isSupplied)) {
Left(Util.format(
"When specifying '%s', at least one of the following options must be provided: %s",
opt.name, list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that all of the options in the list were also supplied if `opt` was supplied.
* @param opt option that depends on all of options in list
* @param list list of dependencies (all will need to be present)
*/
def dependsOnAll(opt: ScallopOption[_], list: List[ScallopOption[_]]) = addValidation {
if (opt.isSupplied && !list.forall(_.isSupplied)) {
Left(Util.format(
"When specifying '%s', all of the following options must also be provided: %s",
opt.name, list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that none of the options in the list were supplied if `opt` was supplied.
* @param opt option that conflicts with all of options in list
* @param list list of dependencies (all will need to be absent)
*/
def conflicts(opt: ScallopOption[_], list: List[ScallopOption[_]]) = addValidation {
if (opt.isSupplied && list.exists(_.isSupplied)) {
val conflict = list.find(_.isSupplied).get
Left(Util.format("Option '%s' conflicts with option '%s'", opt.name, conflict.name))
} else Right(())
}
/** Add a check that at least one of the options is supplied.
* @param list list of options (at least one must be present)
*/
def requireAtLeastOne(list: ScallopOption[_]*) = addValidation {
if (!list.exists(_.isSupplied)) {
Left(Util.format(
"There should be at least one of the following options: %s",
list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that at one and only one option in the list is supplied.
* @param list list of conflicting options (exactly one must be present)
*/
def requireOne(list: ScallopOption[_]*) = addValidation {
if (list.count(_.isSupplied) != 1) {
Left(Util.format(
"There should be exactly one of the following options: %s",
list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that only one or zero of the provided options have values supplied in arguments.
* @param list list of mutually exclusive options
*/
def mutuallyExclusive(list: ScallopOption[_]*) = addValidation {
if (list.count(_.isSupplied) > 1) {
Left(Util.format(
"There should be only one or zero of the following options: %s",
list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that either all or none of the provided options have values supplied in arguments.
* @param list list of codependent options
*/
def codependent(list: ScallopOption[_]*) = addValidation {
val c = list.count(_.isSupplied)
if (c != 0 && c != list.size) {
Left(Util.format(
"Either all or none of the following options should be supplied, because they are co-dependent: %s",
list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Add a check that either all or none of the provided options
* have values defined (either supplied in arguments or got from defaults).
* @param list list of options
*/
def allDefinedOrUndefined(list: ScallopOption[_]*) = addValidation {
val c = list.count(_.toOption.isDefined)
if (c != 0 && c != list.size) {
Left(Util.format(
"Either all or none of the following options should be defined, because they are co-dependent: %s",
list.map(_.name).mkString(", ")
))
} else Right(())
}
/** Validate that file exists. */
def validateFileExists(fileOption: ScallopOption[File]) = addValidation {
fileOption.toOption.fold[Either[String,Unit]](Right(())) { file =>
if (!file.exists) {
Left("File '" + file + "' not found")
} else {
Right(())
}
}
}
/** Validate that file does not exists. */
def validateFileDoesNotExist(fileOption: ScallopOption[File]) = addValidation {
fileOption.toOption
.map(file => {
if (file.exists()) Left(Util.format("File '%s' already exists", file))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that file argument is directory. */
def validateFileIsDirectory(fileOption: ScallopOption[File]) = addValidation {
fileOption.toOption
.map(file => {
if (!file.isDirectory) Left(Util.format("File '%s' is not a directory", file))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that file is not a directory. */
def validateFileIsFile(fileOption: ScallopOption[File]) = addValidation {
fileOption.toOption
.map(file => {
if (!file.isFile) Left(Util.format("File '%s' is not a file", file))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that all the files in the arguments to multi-arg option exist. */
def validateFilesExist(filesOption: ScallopOption[List[File]]) = addValidation {
filesOption.toOption
.map(files => {
val problems = files.filterNot(_.exists)
if (problems.nonEmpty) Left(Util.format("File(s) %s not found", Util.seqstr(problems)))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that all the files in the arguments to multi-arg option do not exist. */
def validateFilesDoNotExist(filesOption: ScallopOption[List[File]]) = addValidation {
filesOption.toOption
.map(files => {
val problems = files.filter(_.exists)
if (problems.nonEmpty) Left(Util.format("File(s) %s already exists", Util.seqstr(problems)))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that all the files in the arguments to multi-arg option are directories. */
def validateFilesIsDirectory(filesOption: ScallopOption[List[File]]) = addValidation {
filesOption.toOption
.map(files => {
val problems = files.filterNot(_.isDirectory)
if (problems.nonEmpty) Left(Util.format("File(s) %s is not a directory", Util.seqstr(problems)))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that all the files in the arguments to multi-arg option are not directories. */
def validateFilesIsFile(filesOption: ScallopOption[List[File]]) = addValidation {
filesOption.toOption
.map(files => {
val problems = files.filterNot(_.isFile)
if (problems.nonEmpty) Left(Util.format("File(s) %s is not a file", Util.seqstr(problems)))
else Right(())
})
.getOrElse(Right(()))
}
/** Validate that path points to the existing file. */
def validatePathExists(pathOption: ScallopOption[Path]) = addValidation {
pathOption.toOption.fold[Either[String,Unit]](Right(())) { path =>
if (!path.toFile.exists) {
Left("File at '" + path + "' not found")
} else {
Right(())
}
}
}
/** Validate that path does not point to the existing file. */
def validatePathDoesNotExist(pathOption: ScallopOption[Path]): Unit = addValidation {
pathOption.toOption
.map {
case path if Files.exists(path) => Left(Util.format("File '%s' already exists", path))
case _ => Right(())
}
.getOrElse(Right(()))
}
/** Validate that path points to a directory. */
def validatePathIsDirectory(pathOption: ScallopOption[Path]): Unit = addValidation {
pathOption.toOption
.map {
case path if Files.isDirectory(path) => Right(())
case path => Left(Util.format("File '%s' is not a directory", path))
}
.getOrElse(Right(()))
}
/** Validate that path points to a file (not directory). */
def validatePathIsFile(pathOption: ScallopOption[Path]): Unit = addValidation {
pathOption.toOption
.map {
case path if Files.isRegularFile(path) => Right(())
case path => Left(Util.format("File '%s' is not a directory", path))
}
.getOrElse(Right(()))
}
/** Validate that path target exists. */
def validatePathsExists(pathsOption: ScallopOption[List[Path]]): Unit = addValidation {
pathsOption.toOption
.map(paths => {
val problems = paths.filterNot(Files.exists(_))
problems match {
case Nil => Right(())
case problem :: Nil => Left(Util.format("File %s not found", problem))
case _ => Left(Util.format("Files %s not found", Util.seqstr(problems)))
}
})
.getOrElse(Right(()))
}
/** Validate that path target does not exist. */
def validatePathsDoesNotExist(pathsOption: ScallopOption[List[Path]]): Unit = addValidation {
pathsOption.toOption
.map(paths => {
val problems = paths.filter(Files.exists(_))
problems match {
case Nil => Right(())
case problem :: Nil => Left(Util.format("File %s already exists", problem))
case _ => Left(Util.format("Files %s already exist", Util.seqstr(problems)))
}
})
.getOrElse(Right(()))
}
/** Validate that paths targets exist. */
def validatePathsIsDirectory(pathsOption: ScallopOption[List[Path]]): Unit = addValidation {
pathsOption.toOption
.map(paths => {
val problems = paths.filterNot(Files.isDirectory(_))
problems match {
case Nil => Right(())
case problem :: Nil => Left(Util.format("File %s is not a directory", problem))
case _ => Left(Util.format("Files %s are not directories", Util.seqstr(problems)))
}
})
.getOrElse(Right(()))
}
/** Validate that all paths targets are files (not directories). */
def validatePathsIsFile(pathsOption: ScallopOption[List[Path]]): Unit = addValidation {
pathsOption.toOption
.map(paths => {
val problems = paths.filterNot(Files.isRegularFile(_))
problems match {
case Nil => Right(())
case problem :: Nil => Left(Util.format("File %s is not a file", problem))
case _ => Left(Util.format("Files %s are not files", Util.seqstr(problems)))
}
})
.getOrElse(Right(()))
}
/** Require subcommand to be provided (validation will fail if no subcommand was provided on the command line). */
def requireSubcommand() = addValidation {
if (subcommand.isEmpty) Left("Subcommand required")
else Right(())
}
// === some getters for convenience ===
/** Get summary of current parser state.
*
* @return a list of all options in the builder, and corresponding values for them.
*/
def summary = {
assertVerified()
builder.summary
}
/** Get summary of current parser state, hididng values for some of the options.
* Useful if you log the summary and want to avoid storing sensitive information
* in the logs (like passwords)
*
* @param blurred names of the options that should be hidden.
* @return a list of all options in the builder
*/
def filteredSummary(blurred: Set[String]): String = {
assertVerified()
builder.filteredSummary(blurred: Set[String])
}
/** Get generated help contents as a string. */
def getHelpString(): String = builder.help
/** Get full generated help contents (with version, banner, option usage and footer) as a string. */
def getFullHelpString(): String = builder.getFullHelpString()
/** Get version string. */
def getVersionString(): Option[String] = builder.vers
/** Prints help message (with version, banner, option usage and footer) to stdout. */
def printHelp() = builder.printHelp()
/** Add a version string to option builder.
*
* @param v Version string.
*/
def version(v: String): Unit = {
editBuilder(_.version(v))
}
/** Add a banner string to option builder.
*
* @param b Banner string.
*/
def banner(b: String): Unit = {
editBuilder(_.banner(b))
}
/** Add a footer string to this builder.
*
* @param f footer string.
*/
def footer(f: String): Unit = {
editBuilder(_.footer(f))
}
/** Explicitly set width of help printout. By default, Scallop tries
* to determine it from terminal width or defaults to 80 characters.
*/
def helpWidth(w: Int): Unit = {
editBuilder(_.setHelpWidth(w))
}
/** If set to true, do not output subcommand options in the help output for the main program
* (only output short subcommand description in such cases).
* Full help for subcommand options can still be accessed via `program subcommand-name --help`.
* @param enable enable short format for subcommand help
*/
def shortSubcommandsHelp(enable: Boolean = true): Unit = {
editBuilder(_.copy(shortSubcommandsHelp = enable))
}
private[scallop] def verifyConf(): Unit = {
// pass option groups into the builder
editBuilder(_.copy(
mainOptions = _mainOptions().toList,
optionGroups = optionGroups.toList.map { g =>
(g.header, g.options.toSeq)
}
))
if (_guessOptionName) {
performOptionNameGuessing()
}
if (builder.opts.exists(_.name.startsWith("\t"))) {
if (optionNameGuessingSupported) {
throw new OptionNameGuessingFailure()
} else {
throw new OptionNameGuessingUnsupported()
}
}
}
/** Verify this configuration - parse the arguments, convert option values, run validations.
* This method MUST be called at the end of all options definitions, attempts to access
* option values before it is called will result in runtime exception.
*/
def verify(): Unit = {
verifyConf()
verifyBuilder()
}
}
/* Convenience variables to permit testing. */
object throwError extends util.DynamicVariable[Boolean](false)
object overrideColorOutput extends util.DynamicVariable[Option[Boolean]](None)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy