Saturnian
Saturnian

Reputation: 1948

How to do a case insensitive match for command line arguments in scala?

I'm working on a command line tool written in Scala which is executed as:

sbt "run --customerAccount 1234567"

Now, I wish to make this flexible to accept "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT ...you get the drift

Here's what the code looks like:

lazy val OptionsParser: OptionParser[Args] = new scopt.OptionParser[Args]("scopt") {
    head(
      "XML Generator",
      "Creates XML for testing"
    )
    help("help").text(s"Prints this usage message. $envUsage")

    opt[String]('c', "customerAccount")
      .text("Required: Please provide customer account number as -c 12334 or --customerAccount 12334")
      .required()
      .action { (cust, args) =>
        assert(cust.nonEmpty, "cust is REQUIRED!!")
        args.copy(cust = cust)
      }
}

I assume the opt[String]('c', "customerAccount") does the pattern matching from the command line and will match with "customerAccount" - how do I get this to match with "--CUSTOMERACCOUNT" or --cUsToMerAccount or --customerACCOUNT? What exactly does the args.copy (cust = cust) do?

I apologize if the questions seem too basic. I'm incredibly new to Scala, have worked in Java and Python earlier so sometimes I find the syntax a little hard to understand as well.

Upvotes: 1

Views: 421

Answers (1)

Levi Ramsey
Levi Ramsey

Reputation: 20561

You'd normally be parsing the args with code like:

OptionsParser.parse(args, Args())

So if you want case-insensitivity, probably the easiest way is to canonicalize the case of args with something like

val canonicalized = args.map(_.toLowerCase)

OptionsParser.parse(canonicalized, Args())

Or, if you for instance wanted to only canonicalize args starting with -- and before a bare --:

val canonicalized =
  args.foldLeft(false -> List.empty[String]) { (state, arg) =>
    val (afterDashes, result) = state
    if (afterDashes) true -> (arg :: result)  // pass through unchanged
    else {
      if (arg == "==") true -> (arg :: result) // move to afterDash state & pass through
      else {
        if (arg.startsWith("--")) false -> (arg.toLowerCase :: result)
        else false -> (arg :: result) // pass through unchanged
      }
    }
  }
  ._2    // Extract the result
  .reverse // Reverse it back into the original order (if building up a sequence, your first choice should be to build a list in reversed order and reverse at the end)

  OptionsParser.parse(canonicalized, Args())

Re the second question, since Args is (almost certainly) a case class, it has a copy method which constructs a new object with (most likely, depending on usage) different values for its fields. So

 args.copy(cust = cust)

creates a new Args object, where:

  • the value of the cust field in that object is the value of the cust variable in that block (this is basically a somewhat clever hack that works with named method arguments)
  • every other field's value is taken from args

Upvotes: 3

Related Questions