Skip to content

Commit

Permalink
Add #lang envy, which uses sweet-exp syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
lexi-lambda committed Oct 1, 2015
1 parent 7c71f90 commit 06b4fee
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 175 deletions.
2 changes: 1 addition & 1 deletion envy/info.rkt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#lang info

(define scribblings '(("scribblings/envy.scrbl")))
(define scribblings '(("scribblings/envy.scrbl" (multi-page))))
10 changes: 10 additions & 0 deletions envy/lang/reader.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#lang s-exp syntax/module-reader envy/s-exp/lang/language

#:read sw:read
#:read-syntax sw:read-syntax

(require (submod sweet-exp link-reader)
(prefix-in tr: typed-racket/typed-reader))

(define-values (sw:read sw:read-syntax)
(sweet-link tr:read tr:read-syntax))
17 changes: 17 additions & 0 deletions envy/s-exp/lang/language.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#lang typed-racket/minimal

(require (rename-in typed/racket/base
[#%module-begin tr:module-begin])
envy
(for-syntax racket/base))

(provide (except-out (all-from-out typed/racket/base)
tr:module-begin)
(rename-out [module-begin #%module-begin]))

(define-syntax (module-begin stx)
(syntax-case stx ()
[(_ clause ...)
(with-syntax ([injected-require (datum->syntax stx '(require envy))])
#'(tr:module-begin injected-require
(define/provide-environment clause ...)))]))
6 changes: 6 additions & 0 deletions envy/s-exp/lang/reader.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#lang s-exp syntax/module-reader envy/s-exp/lang/language

#:read r:read
#:read-syntax r:read-syntax

(require (prefix-in r: typed-racket/typed-reader))
47 changes: 47 additions & 0 deletions envy/scribblings/api-reference.scrbl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#lang scribble/manual

@(require (for-label envy
typed/racket/base))

@title[#:tag "api-reference"]{API Reference}

@defform[#:literals (:)
(define-environment clause ...)
#:grammar
([clause name-id
[name-id maybe-type option ...]]
[maybe-type (code:line)
(code:line : type-id)]
[option (code:line #:name env-var-name-expr)
(code:line #:default default-expr)])]{
Defines a set of variables to be initialized with values from the environment.

Each @racket[name-id] is assigned the value of the environment variable with the name
@racket[env-var-name-expr]. If no @racket[env-var-name-expr] is provided, the environment variable
name is inferred based on @racket[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 @racket[name-id], the value of the environment variable is parsed based on
@racket[type-id]. If no @racket[type-id] is provided, the type is inferred to be @racket[String].
The following types are supported:

@itemlist[
@item{@racket[String]}
@item{@racket[Symbol]}
@item{@racket[Boolean] (must be either @racket["true"] or @racket["false"])}
@item{@racket[Number]}
@item{@racket[Integer]}
@item{@racket[Positive-Integer]}
@item{@racket[Negative-Integer]}
@item{@racket[Nonnegative-Integer]}]

If the specified variable does not exist in the environment, @racket[name-id] is set to the value of
@racket[default-expr]. If no @racket[default-expr] is provided, an error is raised.}

@defform[(define/provide-environment clause ...)]{
Exactly the same as @racket[define-environment] but also @racket[provide]s each @racket[_name-id].}

@defform[(define-environment-variable name-id maybe-type option ...)]{
Exactly the same as @racket[define-environment] but only defines a single variable. The syntax is
the same as @racket[define-environment]'s @racket[_clause].}

178 changes: 5 additions & 173 deletions envy/scribblings/envy.scrbl
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
#lang scribble/manual

@(require (for-label envy
typed/racket/base)
racket/list
scribble/eval)

@(define (make-sandbox)
(let ([eval ((make-eval-factory '() #:lang 'typed/racket))])
(eval '(require envy))
eval))

@(define (make-environment-sandbox environment)
(let ([eval (make-sandbox)]
[make-env-variables-args (make-list (length environment) 'Bytes)])
(eval `(require/typed
racket/base
[#:opaque Environment-Variables environment-variables?]
[current-environment-variables (Parameterof Environment-Variables)]
[make-environment-variables (,@make-env-variables-args -> Environment-Variables)]))
(eval `(current-environment-variables (make-environment-variables ,@environment)))
eval))

@title{Envy: An environment variable manager}

@defmodule[envy]
@defmodule[envy #:lang]

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
Expand All @@ -35,155 +14,8 @@ app's environment variables in one place, automatically wrap them in a module, p
strings into a variety of different datatypes, and even properly associate them with Typed Racket
types.

@section{Quickstart}

To get started, create a module to manage your application's environment variables (e.g.
@code{application.rkt}). Envy can be used by untyped @emph{or} typed Racket modules, but the manifest
module itself @emph{must} be written in Typed Racket.

@codeblock{
#lang typed/racket
(require envy)
}

To specify environment variables, use the @racket[define/provide-environment] form. Within the form,
include the names of the environment variables your application depends on.

@(racketblock
(define/provide-environment
some-environment-variable
another-environment-variable))

Each entry in @racket[define/provide-environment] 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 @tt{ALL_CAPS},
converting dashes to underscores, and stripping question marks. In the above example, Envy would fetch
the values for @tt{SOME_ENVIRONMENT_VARIABLE} and @tt{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.

@(interaction
#:eval (make-sandbox)
(define/provide-environment
some-environment-variable))

If the environment variable @emph{does} exist, its value will be stored in the binding.

@(interaction
#:eval (make-environment-sandbox '(#"SOME_ENVIRONMENT_VARIABLE" #"some value"))
(eval:alts (define/provide-environment
some-environment-variable
another-environment-variable)
(define-environment
some-environment-variable))
some-environment-variable)

To use the values of these environment variables in another module, just @racket[require] the module,
optionally with a prefix.

@(racketblock
(require (prefix-in env: "environment.rkt")))

@subsection{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:

@verbatim{
HOST=racket-lang.org
PARALLEL=true
THREADS=42
}

...one could use the following environment definition:

@(interaction
#:eval (make-environment-sandbox '(#"HOST" #"racket-lang.org"
#"PARALLEL" #"true"
#"THREADS" #"42"))
(eval:alts (define/provide-environment
[host : String]
[parallel? : Boolean]
[threads : Positive-Integer])
(define-environment
[host : String]
[parallel? : Boolean]
[threads : Positive-Integer]))
host
parallel?
threads)

Note that the values are defined with the specified types, useful for Typed Racket users. Also, since
@racket[String] is the default type, including it is not strictly necessary.

@subsection{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
@racket[#:default] option.

@(interaction
#:eval (make-sandbox)
(eval:alts (define/provide-environment
[optional-value #:default #f])
(define-environment
[optional-value #:default #f]))
optional-value)

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.

@subsection{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 @racket[#:name] option.

@(racketblock
(define/provide-environment
[custom-name #:name "ENVIRONMENT_NAME"]))

@section{Reference}

@defform[#:literals (:)
(define-environment clause ...)
#:grammar
([clause name-id
[name-id maybe-type option ...]]
[maybe-type (code:line)
(code:line : type-id)]
[option (code:line #:name env-var-name-expr)
(code:line #:default default-expr)])]{
Defines a set of variables to be initialized with values from the environment.

Each @racket[name-id] is assigned the value of the environment variable with the name
@racket[env-var-name-expr]. If no @racket[env-var-name-expr] is provided, the environment variable
name is inferred based on @racket[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 @racket[name-id], the value of the environment variable is parsed based on
@racket[type-id]. If no @racket[type-id] is provided, the type is inferred to be @racket[String].
The following types are supported:

@itemlist[
@item{@racket[String]}
@item{@racket[Symbol]}
@item{@racket[Boolean] (must be either @racket["true"] or @racket["false"])}
@item{@racket[Number]}
@item{@racket[Integer]}
@item{@racket[Positive-Integer]}
@item{@racket[Negative-Integer]}
@item{@racket[Nonnegative-Integer]}]

If the specified variable does not exist in the environment, @racket[name-id] is set to the value of
@racket[default-expr]. If no @racket[default-expr] is provided, an error is raised.}

@defform[(define/provide-environment clause ...)]{
Exactly the same as @racket[define-environment] but also @racket[provide]s each @racket[_name-id].}
@table-of-contents[]

@defform[(define-environment-variable name-id maybe-type option ...)]{
Exactly the same as @racket[define-environment] but only defines a single variable. The syntax is
the same as @racket[define-environment]'s @racket[_clause].}
@include-section["lang-envy.scrbl"]
@include-section["lang-envy-sexp.scrbl"]
@include-section["api-reference.scrbl"]
41 changes: 41 additions & 0 deletions envy/scribblings/lang-envy-sexp.scrbl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#lang scribble/manual

@(require (for-label envy
typed/racket/base))

@title[#:tag "syntax"]{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
@seclink["top" #:doc '(lib "sweet-exp/sweet.scrbl")]{“sweet expressions”}. The ordinary, S-expression
based syntax is exposed through the @racketmodname[envy/s-exp] language, as well as the
@racketmodname[envy] module, which can be used in any Typed Racket module via @racket[require].

@section{@tt{#lang envy/s-exp}}

@defmodule[envy/s-exp #:lang]

The @racketmodname[envy/s-exp] language works exactly like the @racketmodname[envy] language, but it
does not enable the sweet expression reader. Each declaration must be properly grouped.

@codeblock{
#lang envy/s-exp
[some-var : Positive-Integer #:default #f]
[another-var : Boolean #:name "CUSTOM NAME"]
}

This language works just like using the @racketmodname[envy] module directly, but its body is wrapped
in @racket[define/provide-environment].

@section{The @racketmodname[envy] module}

Using @racketmodname[envy] as a module imports the Envy API, which allows embedding Envy's
functionality in larger modules.

@(racketblock
(require @#,racketmodname[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 @secref["api-reference"].

0 comments on commit 06b4fee

Please sign in to comment.