Dave Griffith
Dave Griffith

Reputation: 20515

Getting command-line arguments in Scala traits

I've got a large number of simple command-line Scala apps which share quite a bit of common structure. All of them inherit from scala.App, which is just fine. I would like to refactor out the shared structure of these command-line Apps into a common trait, which I could then inherit into my (much simpler) command-line app classes. The problem arises in that some of the common structure includes parsing of command-line arguments.

object MyScript extends BaseScript with App{
   //small bits of business logic using components defined in BaseScript
}

trait BaseScript extends App{
    val configuration = loadConfiguration(args(0))
    //setup a bezillion components, usable from any of the scripts, based on the configuration
}

This compiles, but fails with an NPE when it comes time to actually dereference args, presumably because the App trait hasn't yet been initialized. Changing trait orders and changing the inheritance of App in BaseScript to be a self-type declaration do nothing, as have experiments with DelayedInit. Declaring the components as "lazy" in BaseScript would work, but I also wish need to actually use those components during initialization (e.g., setting up log directories and loading JDBC driver classes based on the configuration), so the benefits of laziness are lost. Is there something I can do to get the command-line arguments visible and initialized in the BaseScript trait?

Upvotes: 5

Views: 6016

Answers (3)

Neil Essy
Neil Essy

Reputation: 3607

I think your best bet is to change your BaseScript trait into a class for two reasons. The first is that compared with classes, trait initializations are executed in reverse order. See this question on initialization behavior. Second, BaseScript semantically is more of a superclass than additional behavior. I think you will find that this can simplify things.

When executing MyScript, the following code initializes the BaseScript class first. BaseScript is dependent on the App trait in turn and forces it to initialize first.

object MyScript extends BaseScript {
  //small bits of business logic using components defined in BaseScript
  println( "running" )
  println( "arg(0): " + configuration )
}

class BaseScript extends App {
  val configuration = loadConfiguration(args)
  //setup a bezillion components, usable from any of the scripts, based on the configuration
  def loadConfiguration( args: Array[String] ) = {
    println( "configuring" )
    if ( args.length > 0 ) args(0) else null
  }
}

Upvotes: 5

huynhjl
huynhjl

Reputation: 41646

Looking at the App source it seems you can override main to do things with args before your app code runs:

trait AppUtil extends App {
  def myInit(args: Array[String]) {
    println("args " + args.size)
  }
  override def main(args: Array[String]) {
    myInit(args)
    super.main(args)
  }
}

I suspect the App source can give you inspiration to rewrite your own customized App. The code is really not that long and you'll have more control on things, such as what you do with args, what happens before and after your run main.

Upvotes: 1

oxbow_lakes
oxbow_lakes

Reputation: 134270

Have you tried using lazy val (and not extending the App trait)?

trait BaseScript { self : App =>
  lazy val configuration = loadConfiguration(args(0))
  //setup a bezillion components, usable from any of the scripts
  //based on the configuration
}

Upvotes: 3

Related Questions