Envy: An environment variable manager
#lang envy | package: envy |
All applications need some degree of configuration. Some options can be provided as command-line flags, but often the configuration is too complex to be specified in this way. For all non-trivial configuration, applications should store configuration in the environment to stay organized and to remain portable.
Envy helps keep environment variables in order by creating a way to declaratively specify all of your app’s environment variables in one place, automatically wrap them in a module, parse them from raw strings into a variety of different datatypes, and even properly associate them with Typed Racket types.
1 Quickstart via #lang envy
To get started, create a module to manage your application’s environment variables (e.g. environment.rkt).
#lang envy
To specify which environment variables your application depends on, specify each variable’s name on a separate line:
#lang envy some-environment-variable another-environment-variable
Each line of your environment manifest will produce a variable with the given name bound to the value of the equivalent environment variable. The name of the environment variable will be generated from the name of the Racket variable by converting the identifier to ALL_CAPS, converting dashes to underscores, and stripping question marks. In the above example, Envy would fetch the values for SOME_ENVIRONMENT_VARIABLE and ANOTHER_ENVIRONMENT_VARIABLE.
When the module runs, the values for the specified variables will be loaded, but it’s possible that the variables don’t actually exist in the environment. In this case, an error will be thrown.
#lang envy some-environment-variable
> envy: The required environment variable
"SOME_ENVIRONMENT_VARIABLE" is not defined.
If the environment variable does exist, its value will be stored in the binding.
#lang envy some-environment-variable another-environment-variable
> some-environment-variable - : String
"some value"
To use the values of these environment variables in another module, just require the module, optionally with a prefix.
1.1 Specifying types
All environment variables are natively strings, but it is extremely common to store other kinds of configuration data, such as booleans and numbers. Envy permits specifying a type with each variable, and it will automatically parse the value to match the specified type.
For example, given the following environment:
HOST=racket-lang.org |
PARALLEL=true |
THREADS=42 |
...one could use the following environment definition:
host : String parallel? : Boolean threads : Positive-Integer
> host - : String
"racket-lang.org"
> parallel? - : Boolean
#t
> threads - : Integer [more precisely: Positive-Integer]
42
Note that the values are defined with the specified types, useful for Typed Racket users. Also, since String is the default type, including it is not strictly necessary.
1.2 Providing defaults
Sometimes, configuration variables may be optional, in which case it is useful to provide a default value instead of raising an error upon an undefined variable. This can be done with the #:default option.
optional-value #:default #f
> optional-value - : (U False String)
#f
Note that the type is still properly preserved for Typed Racket users, so if the default value’s type is different from the type of the environment variable, the resulting type will be a union.
1.3 Using explicit environment variable names
Sometimes it is desired to define a variable with a different name from the name used in the environment. This can be done with the #:name option.
custom-name #:name "ENVIRONMENT_NAME"
2 Using Envy with S-expression syntax
Envy’s syntax does not look like traditional S-expressions, but in fact it is just using ordinary Racket syntax with a special reader, called “sweet expressions”. The ordinary, S-expression based syntax is exposed through the envy/s-exp language, as well as the envy module, which can be used in any Typed Racket module via require.
2.1 #lang envy/s-exp
#lang envy/s-exp | package: envy |
The envy/s-exp language works exactly like the envy language, but it does not enable the sweet expression reader. Each declaration must be properly grouped.
#lang envy/s-exp [some-var : Positive-Integer #:default #f] [another-var : Boolean #:name "CUSTOM NAME"]
This language works just like using the envy module directly, but its body is wrapped in define/provide-environment.
2.2 The envy module
Using envy as a module imports the Envy API, which allows embedding Envy’s functionality in larger modules.
(require envy) (define/provide-environment [some-var : Positive-Integer #:default #f] [another-var : Boolean #:name "CUSTOM NAME"])
For full information on all the forms provided by Envy, see the API Reference.
3 API Reference
syntax
(define-environment clause ...)
clause = name-id | [name-id maybe-type option ...] maybe-type =
| : type-id option = #:name env-var-name-expr | #:default default-expr
Each name-id is assigned the value of the environment variable with the name env-var-name-expr. If no env-var-name-expr is provided, the environment variable name is inferred based on name-id: the identifier is converted to all caps, all dashes are converted to underscores, and all question marks are stripped.
Before being assigned to name-id, the value of the environment variable is parsed based on type-id. If no type-id is provided, the type is inferred to be String. The following types are supported:
Boolean (must be either "true" or "false")
If the specified variable does not exist in the environment, name-id is set to the value of default-expr. If no default-expr is provided, an error is raised.
syntax
(define/provide-environment clause ...)
syntax
(define-environment-variable name-id maybe-type option ...)