Flags

An incredibly common pattern in many software projects is parsing command line flags on startup to configure the behavior of a program. For example in backend web services the same service frequently gets reconfigured via flags to run in various different environments (e.g. test/dev/prod). However, in spite of the pattern's universality, most languages seem to ignore the fact and leave Flag parsing as an exercise for the user. This realistically leaves users either running off to download some 3rd-party library or writing some often poorly maintained boilerplate parsing code themselves. Claro aims to provide a lightweight Flag parsing mechanism as a first-class language feature so that you can skip most of the manual toil for such a simple need.

Claro's Flags are a special case of Static Values that can be defined and exported by a Module API1:

Fig 1:


# ex1.claro_module_api

# Set this flag on the command line.
flag env: string;

Then, just like any other Static Value, it can be referenced directly by anyone with a dependency on the defining Module as in the example below:

Fig 2:


load("//@claro-lang:rules.bzl", "claro_module", "claro_binary")

# This module is the one defining the `env` Flag.
claro_module(
    name = "config",
    module_api_file = "ex1.claro_module_api",
    # Notice no srcs are specified as Flags do not require a provider like other Static Values do.
)

claro_binary(
    name = "demo",
    main_file = "ex1-test.claro",
    deps = {"Config": ":config"},
)

Fig 3:


# ex1-test.claro
Config::env |> print("env: \"{^}\"");

Output:

env: ""

Flags are different than general Static Values simply in the way their values are instantiated. Rather than implementing a provider that will be automatically run to instantiate the value, Flags are actually automatically parsed from the command line args passed to the program at runtime. In the example above, the Flag wasn't explicitly set when the program was run, so the value was defaulted to the empty string.

Setting a Flag Value on the Command Line

As there are multiple ways to run Claro programs during development, you'll need to know how to actually set Flag values using each approach.

Passing Flags to Programs Executed via bazel run ...

Of course, as you've seen in the Getting Started Guide the easiest way to run a Claro program during development is using the bazel run ... command. But because Bazel itself accepts command line Flags, you'll need to explicitly indicate which command line args should be consumed by Bazel and which should be passed along to the Claro program. You'll do this by simply using a standalone --. Bazel consumes every arg to the left, and anything following gets passed along to the program you're trying to run.

Note: The below recording was made with asciinema - try pausing and copying any text.

Passing Flags to Deploy Jar

Instead, you can build your program as an executable "Deploy Jar" and execute the Jar using the java command, passing command line Flags as you would to any other command:

Note: The below recording was made with asciinema - try pausing and copying any text.

Deriving Static Values From Flags

Now, the power of Flags is often exposed when used to determine the initialization of Static Values. For example, expanding upon the simple env example above, we could export another Static Value, and determine its value based on whatever value was assigned to the env Flag on the command line.

Fig 4:


# Set this flag on the command line.
flag env: string;

static SUBJECT: string;

Fig 5:


provider static_SUBJECT() -> string {
  # `env` here is referring to the flag defined in this module's API.
  match (env) {
    case "dev" -> return "DEVS";
    case "prod" -> return "Users";
    # If the flag wasn't set, it will default to the empty string.
    case "" -> return "is anyone out there?";
    case _ -> return env;
  }
}

And now, a test program could reference the Static Value, and the program's output will be dependent on the Flag value passed on the command line at runtime:

Fig 6:


# ex1-test2.claro
Config::SUBJECT |> print("Hello, {^}!");

Note: The below recording was made with asciinema - try pausing and copying any text.

Supported Flag Types

Claro has to manually emit logic to parse command line args, and as such there's currently only support for parsing the following basic types that are most likely to be found in command line args:

  • boolean
  • string
  • int
  • [string]

Claro will statically reject any Flags of unsupported types. For example, Claro won't automatically parse arbitrary structs from the command line. (Although it's likely that in the future Claro will standardize its string encoding of all types and provide some extended support for automatically decoding them from strings).


1

Command line Flag parsing in most other languages can only be done by explicitly handling the command line args list in the program's "main method" (or equivalent). But in Claro, Flags can be arbitrarily defined by any Module in the entire program. The only thing to keep in mind is that the very nature of Flags being given on the command line means that their names must be globally unique. So, if you plan to include Flags in a Module that you're publishing for a wide audience, make sure that you use somehow try to ensure that your Flag names can at least be reasonably expected to be globally unique. One suggestion would be to prefix all Flag names with the published name of your Bazel module that's been pushed to the Bazel Central Registry.