Skip to content
imranr edited this page Jan 6, 2014 · 9 revisions

With FieldArgs, sumac will only set the fields from the arguments if they are specified on the command line, this means that if nothing is specified, the field will hold the value assigned to it before the call to parse (usually, the value set at construction time).

class MyArgs extends FieldArgs {
  var willDefault: Int = 0
  var fillMe: String = _
}

val args = new MyArgs

args.parse(Array[String]("--fillMe", "test"))

assert(args.willDefault == 0)
assert(args.fillMe == "test")

Often, hard coding a default in the source is a bad idea. The first solution is to use the @Required Validation annotation, sumac will enforce that the argument is specified on the command line, thus always ignoring the value provided before the parse command is provided.

Defaults in Typesafe Config files

Sumac also provides a mixin trait ConfigArgs that will automatically load the default values from a config file using the Typesafe Config Library, which is available in the sumac-ext package.

class MyArgs extends FieldArgs with ConfigArgs {
  override val configPrefix = "com.example.config"

  var arg1: Option[Duration] = None
}

When using ConfigArgs as a mixin, you have to specify a configPrefix that will be appended to the arguments name to search for a default value in the config files. In the previous example, the default will be loaded from the config key: com.example.config.arg1.

If you use Nesting in your arguments holder, the config key of the nested argument will also be nested in the path:

class Test extends FieldArgs with ConfigArgs {
  override val configPrefix = "com.example.config"

  var arg1: Option[Duration] = None
}

class Nested extends FieldArgs {
  var arg3: Double = 10.5
}


class TestWithNested extends Test {
  var arg2: Nested = new Nested
}

In this example, arg3 will be searched in the config file with the key com.example.config.arg2.arg3.

Config File Loading

The default behaviour is to use the library ConfigFactory.load() to load the config files where to search for the defaults; from config doc (first-listed are higher priority):

  1. system properties
  2. application.conf (all resources on classpath with this name)
  3. application.json (all resources on classpath with this name)
  4. application.properties (all resources on classpath with this name)
  5. reference.conf (all resources on classpath with this name)

The idea is that libraries and frameworks should ship with a reference.conf in their jar. Applications should provide an application.conf.

Default priority & validation

  • code defaults are lowest priority;
  • when using ConfigArgs, the defaults will be loaded from the config file only if no value is provided on the command line;
  • when using multiple default mixins, the defaults are applied in order of the mixins;
  • validation happens after all defaults have been applied

Parsing

While the typesafe config library provides its own parsing library, the values specified in the config files will be parsed with the sumac parsers in the same way as they would be parsed from the command line.

Custom config filenames

You can use the addConfig method to add config files that will take priority over the application.conf file.

If you do not want at all the default loading behaviour of typesafe config to take place, you can set useDefaultConfig to false. Either in the implementation of the arguments holder, or before calling the parse function. In this case, you should specify the name of the conf file to load from the classpath using ConfigFactory.load(filename).

val test = new MyArgs
test.useDefaultConfig = false
test.addConfig("alternate.conf")

test.parse(Array[String]())

Loading a default config file based on a command line argument

It is sometimes not desirable to hardcode the name of the config file to load. A practical use case for this is to have different configuration files for different environments, e.g. dev, staging, production. In this case, it is useful to load the configuration file based on the value of a command line argument. For instance, in our use case, we would have an environment argument: MyApp --env dev.

Sumac provides a simple modular way to perform such a task with the ConfigFromArg mixin:

class LoadFromArg extends FieldArgs with ConfigArgs with ConfigFromArg {
  var env: String = "dev"
  var arg1: Option[Duration] = None

  useDefaultConfig = false

  override lazy val configFilenameFromArg: Option[String] = {
    env match {
      case "production" => Some("application.conf")
      case "dev" => Some("alternate.conf")
      case _ => None
    }
  }
}

When using this mixin, you need to implement the configFilenameFromArgs: Option[String] method (be careful, it cannot be overridden with a val). This method can use any field to decide of the config file to load. At the time of calling the configFilenameFromArgs method, the command line arguments will have been parsed and assigned to their field holders and all the previous default mixins will have been applied.