├── .gitignore ├── .travis.yml ├── README.md ├── deploy-docs.sh ├── envy ├── environment.rkt ├── info.rkt ├── lang │ └── reader.rkt ├── main.rkt ├── private │ └── coerce.rkt ├── s-exp │ └── lang │ │ ├── language.rkt │ │ └── reader.rkt └── scribblings │ ├── api-reference.scrbl │ ├── envy.scrbl │ ├── lang-envy-sexp.scrbl │ ├── lang-envy.scrbl │ └── util.rkt ├── info.rkt └── tests └── envy └── environment.rkt /.gitignore: -------------------------------------------------------------------------------- 1 | # Racket compiled files 2 | compiled/ 3 | 4 | # Scribble documentation files 5 | doc/ 6 | docs/ 7 | 8 | # cover-generated code coverage reports 9 | coverage/ 10 | 11 | # common backups, autosaves, lock files, OS meta-files 12 | *~ 13 | \#* 14 | .#* 15 | .DS_Store 16 | *.bak 17 | TAGS 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | sudo: false 4 | 5 | env: 6 | global: 7 | - GH_REF: 'github.com/lexi-lambda/envy.git' 8 | - secure: "n1KL/bsIonALINjDlYEmUgekwJ0Aop5jdP08JX5ldcDfj5+hsb5LgRywOxlysAKf/lbQ/lCREmuRXBMnt8JTID08CvwAtNq+WgFunuuIu/LNNwCCgjsLGcFDEjlx/4WgcMBqoXFU08aS9ExL0okwRSWVnOK4GAbyBcNd6aTwefrwEgntxJjf8yCXUhjnAPAxhYstuYOQntp1wqqsrHjjlnNfz4p59Sp7ckboi7RXgySCuTpcRktK+WVuLQWukrM+RF/sSz4PZAAQ/x/Ges6SOakqtAYFcysU9AE+pftSG9xztUWrekH4jb3+/JqfGgHmvafwFAsLITTJ0ctOGcwozXi6m0249q+eyidP6TTaZy+NvvEyEsL0WxQND6x420Isj/01nZHgLmlqEuXlPkWj1Bym5WK0b6+deWfgGLaBrw5XKt4znP4GLoF1ImhjzyXwdSh0eYv1U/xvOdeiTna/q9iGJ5zVzqtjIXIxvO0HgEpw1qgMGUdDMJGyfjqWtvk+AKRSc3Tk+VPL1MxsEfvYrLYY3xZ6/tZGeZGzPfY7r0TEnNjVkUhsAFaIApPagM7wMNcvLMWyldHLszsGZbT0DJbJHx2s7izdSTgfdfx8tqlEIB1esNM5ZDwZ2aTAXJL0CWpeGuTMWMNmccCoPTIBNaV6BNOYocuKzDKkTJ/u+mI=" 9 | - RACKET_DIR: '~/racket' 10 | matrix: 11 | - RACKET_VERSION: HEAD 12 | 13 | before_install: 14 | - git clone https://github.com/greghendershott/travis-racket.git 15 | - cat travis-racket/install-racket.sh | bash 16 | - export PATH="${RACKET_DIR}/bin:${PATH}" 17 | 18 | install: 19 | - raco pkg install -in envy --auto --link $TRAVIS_BUILD_DIR 20 | 21 | before_script: 22 | 23 | script: 24 | - raco test -p envy 25 | 26 | after_success: 27 | - bash deploy-docs.sh 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Envy [![Build Status](https://travis-ci.org/lexi-lambda/envy.svg?branch=master)](https://travis-ci.org/lexi-lambda/envy) [![Issues in Backlog](https://badge.waffle.io/lexi-lambda/envy.svg?label=backlog&title=Backlog)](http://waffle.io/lexi-lambda/envy) 2 | 3 | Envy is an environment variable manager for Racket applications. 4 | 5 | - Specify your environment variables in a declarative manifest, then use them as plain Racket variables. 6 | - Automatically fail with helpful error messages when required environment variables are not present. 7 | - Include types on your variables to automatically parse string values into Racket datatypes. 8 | 9 | Envy supports plain Racket and Typed Racket out of the box; just install the `envy` package and go! 10 | 11 | For information on how to get started, [take a look at the documentation][docs]. 12 | 13 | ## Credits 14 | 15 | Name and functionality inspired by [Envied][envied] for Ruby. 16 | 17 | [docs]: https://lexi-lambda.github.io/envy/envy.html 18 | [envied]: https://github.com/eval/envied 19 | -------------------------------------------------------------------------------- /deploy-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ev # exit with nonzero exit code if anything fails 3 | 4 | if [[ "$TRAVIS_BRANCH" != 'master' ]]; then 5 | exit 0; 6 | fi 7 | 8 | # clear the documentation directory 9 | rm -rf docs || exit 0; 10 | 11 | # build the documentation files 12 | scribble +m --redirect-main http://pkg-build.racket-lang.org/doc/ --html --dest ./docs ./envy/scribblings/envy.scrbl 13 | 14 | # go to the documentation directory and create a *new* Git repo 15 | cd docs 16 | git init 17 | 18 | # inside this git repo we'll pretend to be a new user 19 | git config user.name 'Travis CI' 20 | git config user.email 'lexi.lambda@gmail.com' 21 | 22 | # The first and only commit to this new Git repo contains all the 23 | # files present with the commit message "Deploy to GitHub Pages". 24 | git add . 25 | git commit -m 'Deploy to GitHub Pages' 26 | 27 | # Force push from the current repo's master branch to the remote 28 | # repo. (All previous history on the branch will be lost, since we are 29 | # overwriting it.) We redirect any output to /dev/null to hide any sensitive 30 | # credential data that might otherwise be exposed. 31 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1 32 | -------------------------------------------------------------------------------- /envy/environment.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket/base 2 | 3 | (require (for-syntax racket/base 4 | racket/dict 5 | racket/string 6 | racket/syntax 7 | syntax/id-table 8 | threading) 9 | syntax/parse/define 10 | "private/coerce.rkt") 11 | 12 | (provide define-environment 13 | define/provide-environment 14 | define-environment-variable) 15 | 16 | (define-for-syntax auto-type-table 17 | (make-immutable-free-id-table 18 | `((,#'String . ,#'(inst values String)) 19 | (,#'Symbol . ,#'string->symbol) 20 | (,#'Boolean . ,#'string->boolean) 21 | (,#'Number . ,#'string->number) 22 | (,#'Integer . ,#'string->integer) 23 | (,#'Positive-Integer . ,#'string->positive-integer) 24 | (,#'Negative-Integer . ,#'string->negative-integer) 25 | (,#'Nonnegative-Integer . ,#'string->nonnegative-integer)))) 26 | 27 | (define-syntax-parser define-environment-variable 28 | #:literals (:) 29 | [(_ (~describe "name" name:id) 30 | (~optional (~describe "type" (~seq : type:id)) #:defaults ([type #'String])) 31 | (~or (~optional (~seq #:name env-var-name:expr) #:defaults ([env-var-name #f])) 32 | (~optional (~seq #:default default:expr) #:defaults ([default #f]))) 33 | ...) 34 | (with-syntax* ([env-var-name (or (attribute env-var-name) 35 | (~> (syntax-e #'name) 36 | symbol->string 37 | string-upcase 38 | (string-replace "-" "_") 39 | (string-replace "?" "")))] 40 | [coerce (dict-ref auto-type-table #'type)] 41 | [fetch-env-var 42 | (if (attribute default) 43 | #'(require-environment-variable env-var-name coerce default) 44 | #'(require-environment-variable env-var-name coerce))]) 45 | #'(define name fetch-env-var))]) 46 | 47 | (begin-for-syntax 48 | (define-syntax-class environment-clause 49 | #:attributes (name normalized) 50 | (pattern name:id #:with normalized #'[name]) 51 | (pattern [name:id args ...] #:with normalized #'[name args ...]))) 52 | 53 | (define-syntax-parser define-environment 54 | [(_ clause:environment-clause ...) 55 | (with-syntax ([((normalized ...) ...) #'(clause.normalized ...)]) 56 | #'(begin (define-environment-variable normalized ...) ...))]) 57 | 58 | (define-syntax-parser define/provide-environment 59 | [(_ clause:environment-clause ...) 60 | (with-syntax ([(name ...) #'(clause.name ...)] 61 | [((normalized ...) ...) #'(clause.normalized ...)]) 62 | #'(begin (begin (define-environment-variable normalized ...) 63 | (provide name)) 64 | ...))]) 65 | 66 | (: require-environment-variable (All [a b] (case-> (String (String -> a) -> a) 67 | (String (String -> a) b -> (U a b))))) 68 | (define require-environment-variable 69 | (case-lambda 70 | [(name parse) 71 | (let ([value (getenv name)]) 72 | (unless value 73 | (error 'envy "The required environment variable \"~a\" is not defined." name)) 74 | (parse value))] 75 | [(name parse default) 76 | (let ([value (getenv name)]) 77 | (if value (parse value) default))])) 78 | -------------------------------------------------------------------------------- /envy/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define scribblings '(("scribblings/envy.scrbl" (multi-page)))) 4 | -------------------------------------------------------------------------------- /envy/lang/reader.rkt: -------------------------------------------------------------------------------- 1 | #lang s-exp syntax/module-reader envy/s-exp/lang/language 2 | 3 | #:read sweet-read 4 | #:read-syntax sweet-read-syntax 5 | 6 | (require sweet-exp/reader) 7 | -------------------------------------------------------------------------------- /envy/main.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket/base 2 | 3 | (require envy/environment) 4 | (provide (all-from-out envy/environment)) 5 | -------------------------------------------------------------------------------- /envy/private/coerce.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket/base 2 | 3 | ;; This module implements typed coercion functions for converting strings to various datatypes. 4 | 5 | (require threading) 6 | 7 | (provide string->integer 8 | string->positive-integer 9 | string->negative-integer 10 | string->nonnegative-integer 11 | string->boolean) 12 | 13 | ;; Numeric Tower 14 | 15 | (define-syntax define-string->number-coercion 16 | (syntax-rules (:) 17 | [(_ name : Type) 18 | (begin 19 | (: name (String -> Type)) 20 | (define name (λ~> string->number 21 | (cast Type))))])) 22 | 23 | (define-string->number-coercion 24 | string->integer : Integer) 25 | 26 | (define-string->number-coercion 27 | string->positive-integer : Positive-Integer) 28 | 29 | (define-string->number-coercion 30 | string->negative-integer : Negative-Integer) 31 | 32 | (define-string->number-coercion 33 | string->nonnegative-integer : Nonnegative-Integer) 34 | 35 | ;; Miscellaneous Types 36 | 37 | (define (string->boolean [str : String]) : Boolean 38 | (case str 39 | [("true") #t] 40 | [("false") #f] 41 | [else (cast str Boolean)])) 42 | -------------------------------------------------------------------------------- /envy/s-exp/lang/language.rkt: -------------------------------------------------------------------------------- 1 | #lang typed-racket/minimal 2 | 3 | (require (rename-in typed/racket/base 4 | [#%module-begin tr:module-begin]) 5 | envy 6 | (for-syntax racket/base)) 7 | 8 | (provide (except-out (all-from-out typed/racket/base) 9 | tr:module-begin) 10 | (rename-out [module-begin #%module-begin])) 11 | 12 | (define-syntax (module-begin stx) 13 | (syntax-case stx () 14 | [(_ clause ...) 15 | (with-syntax ([injected-require (datum->syntax stx '(require envy))]) 16 | #'(tr:module-begin injected-require 17 | (define/provide-environment clause ...)))])) 18 | -------------------------------------------------------------------------------- /envy/s-exp/lang/reader.rkt: -------------------------------------------------------------------------------- 1 | #lang s-exp syntax/module-reader envy/s-exp/lang/language 2 | 3 | #:read r:read 4 | #:read-syntax r:read-syntax 5 | 6 | (require (prefix-in r: typed-racket/typed-reader)) 7 | -------------------------------------------------------------------------------- /envy/scribblings/api-reference.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @(require (for-label envy 4 | typed/racket/base)) 5 | 6 | @title[#:tag "api-reference"]{API Reference} 7 | 8 | @defform[#:literals (:) 9 | (define-environment clause ...) 10 | #:grammar 11 | ([clause name-id 12 | [name-id maybe-type option ...]] 13 | [maybe-type (code:line) 14 | (code:line : type-id)] 15 | [option (code:line #:name env-var-name-expr) 16 | (code:line #:default default-expr)])]{ 17 | Defines a set of variables to be initialized with values from the environment. 18 | 19 | Each @racket[name-id] is assigned the value of the environment variable with the name 20 | @racket[env-var-name-expr]. If no @racket[env-var-name-expr] is provided, the environment variable 21 | name is inferred based on @racket[name-id]: the identifier is converted to all caps, all dashes are 22 | converted to underscores, and all question marks are stripped. 23 | 24 | Before being assigned to @racket[name-id], the value of the environment variable is parsed based on 25 | @racket[type-id]. If no @racket[type-id] is provided, the type is inferred to be @racket[String]. 26 | The following types are supported: 27 | 28 | @itemlist[ 29 | @item{@racket[String]} 30 | @item{@racket[Symbol]} 31 | @item{@racket[Boolean] (must be either @racket["true"] or @racket["false"])} 32 | @item{@racket[Number]} 33 | @item{@racket[Integer]} 34 | @item{@racket[Positive-Integer]} 35 | @item{@racket[Negative-Integer]} 36 | @item{@racket[Nonnegative-Integer]}] 37 | 38 | If the specified variable does not exist in the environment, @racket[name-id] is set to the value of 39 | @racket[default-expr]. If no @racket[default-expr] is provided, an error is raised.} 40 | 41 | @defform[(define/provide-environment clause ...)]{ 42 | Exactly the same as @racket[define-environment] but also @racket[provide]s each @racket[_name-id].} 43 | 44 | @defform[(define-environment-variable name-id maybe-type option ...)]{ 45 | Exactly the same as @racket[define-environment] but only defines a single variable. The syntax is 46 | the same as @racket[define-environment]'s @racket[_clause].} 47 | 48 | -------------------------------------------------------------------------------- /envy/scribblings/envy.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @title{Envy: An environment variable manager} 4 | 5 | @defmodule[envy #:lang] 6 | 7 | All applications need some degree of configuration. Some options can be provided as command-line 8 | flags, but often the configuration is too complex to be specified in this way. For all non-trivial 9 | configuration, @link["http://12factor.net/config"]{applications should store configuration in the 10 | environment} to stay organized and to remain portable. 11 | 12 | Envy helps keep environment variables in order by creating a way to declaratively specify all of your 13 | app's environment variables in one place, automatically wrap them in a module, parse them from raw 14 | strings into a variety of different datatypes, and even properly associate them with Typed Racket 15 | types. 16 | 17 | @table-of-contents[] 18 | 19 | @include-section["lang-envy.scrbl"] 20 | @include-section["lang-envy-sexp.scrbl"] 21 | @include-section["api-reference.scrbl"] -------------------------------------------------------------------------------- /envy/scribblings/lang-envy-sexp.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @(require (for-label envy 4 | typed/racket/base)) 5 | 6 | @title[#:tag "syntax"]{Using Envy with S-expression syntax} 7 | 8 | Envy's syntax does not look like traditional S-expressions, but in fact it is just using ordinary 9 | Racket syntax with a special reader, called 10 | @seclink["top" #:doc '(lib "sweet-exp/sweet.scrbl")]{“sweet expressions”}. The ordinary, S-expression 11 | based syntax is exposed through the @racketmodname[envy/s-exp] language, as well as the 12 | @racketmodname[envy] module, which can be used in any Typed Racket module via @racket[require]. 13 | 14 | @section{@tt{#lang envy/s-exp}} 15 | 16 | @defmodule[envy/s-exp #:lang] 17 | 18 | The @racketmodname[envy/s-exp] language works exactly like the @racketmodname[envy] language, but it 19 | does not enable the sweet expression reader. Each declaration must be properly grouped. 20 | 21 | @codeblock{ 22 | #lang envy/s-exp 23 | [some-var : Positive-Integer #:default #f] 24 | [another-var : Boolean #:name "CUSTOM NAME"] 25 | } 26 | 27 | This language works just like using the @racketmodname[envy] module directly, but its body is wrapped 28 | in @racket[define/provide-environment]. 29 | 30 | @section{The @racketmodname[envy] module} 31 | 32 | Using @racketmodname[envy] as a module imports the Envy API, which allows embedding Envy's 33 | functionality in larger modules. 34 | 35 | @(racketblock 36 | (require @#,racketmodname[envy]) 37 | (define/provide-environment 38 | [some-var : Positive-Integer #:default #f] 39 | [another-var : Boolean #:name "CUSTOM NAME"])) 40 | 41 | For full information on all the forms provided by Envy, see the @secref["api-reference"]. 42 | -------------------------------------------------------------------------------- /envy/scribblings/lang-envy.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @(require (for-label envy 4 | typed/racket/base) 5 | scribble/eval 6 | "util.rkt") 7 | 8 | @; --------------------------------------------------------------------------------------------------- 9 | 10 | @title[#:tag "quickstart"]{Quickstart via @tt{#lang envy}} 11 | 12 | To get started, create a module to manage your application's environment variables (e.g. 13 | @code{environment.rkt}). 14 | 15 | @codeblock{ 16 | #lang envy 17 | } 18 | 19 | To specify which environment variables your application depends on, specify each variable's name on a 20 | separate line: 21 | 22 | @codeblock{ 23 | #lang envy 24 | some-environment-variable 25 | another-environment-variable 26 | } 27 | 28 | Each line of your environment manifest will produce a variable with the given name bound to the value 29 | of the equivalent environment variable. The name of the environment variable will be generated from 30 | the name of the Racket variable by converting the identifier to @tt{ALL_CAPS}, converting dashes to 31 | underscores, and stripping question marks. In the above example, Envy would fetch the values for 32 | @tt{SOME_ENVIRONMENT_VARIABLE} and @tt{ANOTHER_ENVIRONMENT_VARIABLE}. 33 | 34 | When the module runs, the values for the specified variables will be loaded, but it's possible that 35 | the variables don't actually exist in the environment. In this case, an error will be thrown. 36 | 37 | @codeblock{ 38 | #lang envy 39 | some-environment-variable 40 | } 41 | 42 | @(interaction 43 | #:eval (make-sandbox) 44 | (eval:alts code:blank 45 | (define-environment 46 | some-environment-variable))) 47 | 48 | If the environment variable @emph{does} exist, its value will be stored in the binding. 49 | 50 | @codeblock{ 51 | #lang envy 52 | some-environment-variable 53 | another-environment-variable 54 | } 55 | 56 | @(interaction 57 | #:eval (make-environment-sandbox '(#"SOME_ENVIRONMENT_VARIABLE" #"some value")) 58 | (eval:alts some-environment-variable 59 | (begin (define-environment 60 | some-environment-variable) 61 | some-environment-variable))) 62 | 63 | To use the values of these environment variables in another module, just @racket[require] the module, 64 | optionally with a prefix. 65 | 66 | @(racketblock 67 | (require (prefix-in env: "environment.rkt"))) 68 | 69 | @; --------------------------------------------------------------------------------------------------- 70 | 71 | @section{Specifying types} 72 | 73 | All environment variables are natively strings, but it is extremely common to store other kinds of 74 | configuration data, such as booleans and numbers. Envy permits specifying a type with each variable, 75 | and it will automatically parse the value to match the specified type. 76 | 77 | For example, given the following environment: 78 | 79 | @verbatim{ 80 | HOST=racket-lang.org 81 | PARALLEL=true 82 | THREADS=42 83 | } 84 | 85 | ...one could use the following environment definition: 86 | 87 | @codeblock{ 88 | host : String 89 | parallel? : Boolean 90 | threads : Positive-Integer 91 | } 92 | 93 | @(interaction 94 | #:eval (make-environment-sandbox '(#"HOST" #"racket-lang.org" 95 | #"PARALLEL" #"true" 96 | #"THREADS" #"42")) 97 | (eval:alts host 98 | (begin (define-environment 99 | [host : String] 100 | [parallel? : Boolean] 101 | [threads : Positive-Integer]) 102 | host)) 103 | parallel? 104 | threads) 105 | 106 | Note that the values are defined with the specified types, useful for Typed Racket users. Also, since 107 | @racket[String] is the default type, including it is not strictly necessary. 108 | 109 | @; --------------------------------------------------------------------------------------------------- 110 | 111 | @section{Providing defaults} 112 | 113 | Sometimes, configuration variables may be optional, in which case it is useful to provide a default 114 | value instead of raising an error upon an undefined variable. This can be done with the 115 | @racket[#:default] option. 116 | 117 | @codeblock{ 118 | optional-value #:default #f 119 | } 120 | 121 | @(interaction 122 | #:eval (make-sandbox) 123 | (eval:alts optional-value 124 | (begin 125 | (define-environment 126 | [optional-value #:default #f]) 127 | optional-value))) 128 | 129 | Note that the type is still properly preserved for Typed Racket users, so if the default value's type 130 | is different from the type of the environment variable, the resulting type will be a union. 131 | 132 | @; --------------------------------------------------------------------------------------------------- 133 | 134 | @section{Using explicit environment variable names} 135 | 136 | Sometimes it is desired to define a variable with a different name from the name used in the 137 | environment. This can be done with the @racket[#:name] option. 138 | 139 | @codeblock{ 140 | custom-name #:name "ENVIRONMENT_NAME" 141 | } 142 | -------------------------------------------------------------------------------- /envy/scribblings/util.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/list 4 | scribble/eval) 5 | 6 | (provide make-sandbox make-environment-sandbox) 7 | 8 | (define (make-sandbox) 9 | (let ([eval ((make-eval-factory '() #:lang 'typed/racket))]) 10 | (eval '(require envy)) 11 | eval)) 12 | 13 | (define (make-environment-sandbox environment) 14 | (let ([eval (make-sandbox)] 15 | [make-env-variables-args (make-list (length environment) 'Bytes)]) 16 | (eval `(require/typed 17 | racket/base 18 | [#:opaque Environment-Variables environment-variables?] 19 | [current-environment-variables (Parameterof Environment-Variables)] 20 | [make-environment-variables (,@make-env-variables-args -> Environment-Variables)])) 21 | (eval `(current-environment-variables (make-environment-variables ,@environment))) 22 | eval)) 23 | -------------------------------------------------------------------------------- /info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define name "envy") 4 | (define pkg-desc "an environment variable manager") 5 | (define version "1.0.0") 6 | 7 | (define collection 'multi) 8 | 9 | ; #lang envy generates code that uses Typed Racket 10 | (define implies 11 | '("typed-racket-lib")) 12 | 13 | (define deps 14 | '("base" 15 | "sweet-exp-lib" 16 | "threading" 17 | "typed-racket-lib")) 18 | (define build-deps 19 | '("racket-doc" 20 | "scribble-lib" 21 | "sweet-exp" 22 | "typed-racket-doc" 23 | "typed-racket-more")) 24 | -------------------------------------------------------------------------------- /tests/envy/environment.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket/base 2 | 3 | (require envy/environment 4 | typed/rackunit) 5 | 6 | (require/typed 7 | racket/base 8 | [#:opaque Environment-Variables environment-variables?] 9 | [current-environment-variables (Parameterof Environment-Variables)] 10 | [make-environment-variables (Bytes Bytes Bytes Bytes -> Environment-Variables)]) 11 | 12 | (parameterize ([current-environment-variables (make-environment-variables #"VAR_A" #"value-a" 13 | #"VAR_B" #"value-b")]) 14 | (define-environment-variable var-a) 15 | (define-environment-variable var-b) 16 | (check-equal? var-a "value-a") 17 | (check-equal? var-b "value-b") 18 | 19 | (check-exn #rx"The required environment variable \"VAR_C\" is not defined." 20 | (lambda () (define-environment-variable var-c) (void)))) 21 | 22 | (parameterize ([current-environment-variables (make-environment-variables #"A_BOOLEAN" #"true" 23 | #"A_INTEGER" #"42")]) 24 | (define-environment-variable a-boolean : Boolean) 25 | (define-environment-variable a-integer : Integer) 26 | (check-equal? a-boolean #t) 27 | (check-equal? a-integer 42)) 28 | --------------------------------------------------------------------------------