src/Pure/System/getopts.scala
author wenzelm
Fri, 07 Nov 2025 16:43:04 +0100
changeset 83521 e3bad2e60f65
parent 82719 2d99f3e24da4
permissions -rw-r--r--
support for internal commands, without global Output nor sys.exit;
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     1
/*  Title:      Pure/System/getopts.scala
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     2
    Author:     Makarius
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     3
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     4
Support for command-line options as in GNU bash.
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     5
*/
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     6
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     7
package isabelle
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     8
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
     9
71601
97ccf48c2f0c misc tuning based on hints by IntelliJ IDEA;
wenzelm
parents: 71383
diff changeset
    10
import scala.annotation.tailrec
97ccf48c2f0c misc tuning based on hints by IntelliJ IDEA;
wenzelm
parents: 71383
diff changeset
    11
97ccf48c2f0c misc tuning based on hints by IntelliJ IDEA;
wenzelm
parents: 71383
diff changeset
    12
75393
87ebf5a50283 clarified formatting, for the sake of scala3;
wenzelm
parents: 74306
diff changeset
    13
object Getopts {
82719
2d99f3e24da4 support dynamic usage_text, after some options have been processed already;
wenzelm
parents: 75393
diff changeset
    14
  def apply(usage_text: => String, option_specs: (String, String => Unit)*): Getopts = {
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    15
    val options =
73359
d8a0e996614b tuned --- fewer warnings;
wenzelm
parents: 71601
diff changeset
    16
      option_specs.foldLeft(Map.empty[Char, (Boolean, String => Unit)]) {
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    17
        case (m, (s, f)) =>
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    18
          val (a, entry) =
74037
c13198575f75 tuned --- based on hints by IntelliJ IDEA;
wenzelm
parents: 73359
diff changeset
    19
            if (s.length == 1) (s(0), (false, f))
c13198575f75 tuned --- based on hints by IntelliJ IDEA;
wenzelm
parents: 73359
diff changeset
    20
            else if (s.length == 2 && s.endsWith(":")) (s(0), (true, f))
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    21
            else error("Bad option specification: " + quote(s))
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    22
          if (m.isDefinedAt(a)) error("Duplicate option specification: " + quote(a.toString))
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    23
          else m + (a -> entry)
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    24
      }
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    25
    new Getopts(usage_text, options)
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    26
  }
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    27
}
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    28
82719
2d99f3e24da4 support dynamic usage_text, after some options have been processed already;
wenzelm
parents: 75393
diff changeset
    29
class Getopts private(usage_text: => String, options: Map[Char, (Boolean, String => Unit)]) {
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    30
  def usage(internal: Boolean = false): Nothing = {
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    31
    if (internal) error(usage_text)
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    32
    else {
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    33
      Output.writeln(usage_text, stdout = true)
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    34
      sys.exit(Process_Result.RC.error)
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    35
    }
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    36
  }
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    37
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    38
  private def is_option(opt: Char): Boolean = options.isDefinedAt(opt)
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    39
  private def is_option0(opt: Char): Boolean = is_option(opt) && !options(opt)._1
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    40
  private def is_option1(opt: Char): Boolean = is_option(opt) && options(opt)._1
62432
183c319b26dc tuned messages;
wenzelm
parents: 62431
diff changeset
    41
  private def print_option(opt: Char): String = quote("-" + opt.toString)
62434
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    42
  private def option(opt: Char, opt_arg: List[Char]): Unit =
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    43
    try { options(opt)._2.apply(opt_arg.mkString) }
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    44
    catch {
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    45
      case ERROR(msg) =>
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    46
        cat_error(msg, "The error(s) above occurred in command-line option " + print_option(opt))
4630b1748cb3 tuned messages;
wenzelm
parents: 62432
diff changeset
    47
    }
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    48
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    49
  private def err(msg: String, internal: Boolean): Nothing =
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    50
    if (internal) cat_error(msg, usage_text)
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    51
    else { Output.error_message(msg); usage() }
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    52
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    53
  @tailrec private def getopts(args: List[List[Char]], internal: Boolean): List[List[Char]] =
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    54
    args match {
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    55
      case List('-', '-') :: rest_args => rest_args
71383
8313dca6dee9 misc tuning, following hint by IntelliJ;
wenzelm
parents: 67178
diff changeset
    56
      case ('-' :: opt :: rest_opts) :: rest_args if is_option0(opt) && rest_opts.nonEmpty =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    57
        option(opt, Nil); getopts(('-' :: rest_opts) :: rest_args, internal)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    58
      case List('-', opt) :: rest_args if is_option0(opt) =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    59
        option(opt, Nil); getopts(rest_args, internal)
71383
8313dca6dee9 misc tuning, following hint by IntelliJ;
wenzelm
parents: 67178
diff changeset
    60
      case ('-' :: opt :: opt_arg) :: rest_args if is_option1(opt) && opt_arg.nonEmpty =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    61
        option(opt, opt_arg); getopts(rest_args, internal)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    62
      case List('-', opt) :: opt_arg :: rest_args if is_option1(opt) =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    63
        option(opt, opt_arg); getopts(rest_args, internal)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    64
      case List(List('-', opt)) if is_option1(opt) =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    65
        err("Command-line option " + print_option(opt) + " requires an argument", internal)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    66
      case ('-' :: opt :: _) :: _ if !is_option(opt) =>
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    67
        err(if (opt != '?') "Illegal command-line option " + print_option(opt) else "", internal)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    68
      case _ => args
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    69
  }
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    70
83521
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    71
  def apply(args: List[String], internal: Boolean): List[String] =
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    72
    getopts(args.map(_.toList), internal).map(_.mkString)
e3bad2e60f65 support for internal commands, without global Output nor sys.exit;
wenzelm
parents: 82719
diff changeset
    73
  def apply(args: List[String]): List[String] = apply(args, false)
62431
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    74
  def apply(args: Array[String]): List[String] = apply(args.toList)
fbccea37091d support for command-line options as in GNU bash;
wenzelm
parents:
diff changeset
    75
}