Merge remote-tracking branch 'origin/main' into csv_decoding-server_example

This commit is contained in:
Folkert 2022-09-28 16:38:40 +02:00
commit 2e5f207283
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
14 changed files with 271 additions and 59 deletions

View file

@ -6,3 +6,4 @@ form
tui
http-get
file-io
env

View file

@ -1,6 +1,5 @@
interface Env
exposes [cwd, dict, var]
imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask]
exposes [cwd, dict, var, decode] imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask, EnvDecoding]
## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
## from the environment. File operations on relative [Path]s are relative to this directory.
@ -33,31 +32,40 @@ var = \name ->
|> Effect.map (\result -> Result.mapErr result \{} -> VarNotFound)
|> InternalTask.fromEffect
# ## Reads the given environment variable and attempts to decode it.
# ##
# ## The type being decoded into will be determined by type inference. For example,
# ## if this ends up being used like a `Task U16 …` then the environment variable
# ## will be decoded as a string representation of a `U16`.
# ##
# ## getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError]* [Env]*
# ## getU16Var = \var -> Env.decode var
# ## # If the environment contains a variable NUM_THINGS=123, then calling
# ## # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123.
# ## #
# ## # However, if the NUM_THINGS environment variable was set to 1234567, then
# ## # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16.
# ##
# ## Supported types:
# ## - strings
# ## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers
# ## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas
# ##
# ## Trying to decode into any other types will always fail with a `DecodeErr`.
# decode : Str -> Task val [VarNotFound, DecodeErr DecodeError]* [Env]*
# | val has Decode
# decode = \var ->
# Effect.envVar var
# |> InternalTask.fromEffect
## Reads the given environment variable and attempts to decode it.
##
## The type being decoded into will be determined by type inference. For example,
## if this ends up being used like a `Task U16 …` then the environment variable
## will be decoded as a string representation of a `U16`.
##
## getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError]* [Env]*
## getU16Var = \var -> Env.decode var
## # If the environment contains a variable NUM_THINGS=123, then calling
## # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123.
## #
## # However, if the NUM_THINGS environment variable was set to 1234567, then
## # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16.
##
## Supported types:
## - strings
## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers
## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas
##
## Trying to decode into any other types will always fail with a `DecodeErr`.
decode : Str -> Task val [VarNotFound, DecodeErr DecodeError]* [Env]* | val has Decoding
decode = \name ->
Effect.envVar name
|> Effect.map
(
\result ->
result
|> Result.mapErr (\{} -> VarNotFound)
|> Result.try
(\varStr ->
Decode.fromBytes (Str.toUtf8 varStr) (EnvDecoding.format {})
|> Result.mapErr (\_ -> DecodeErr TooShort)))
|> InternalTask.fromEffect
## Reads all the process's environment variables into a [Dict].
##
## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)

View file

@ -0,0 +1,95 @@
interface EnvDecoding exposes [EnvFormat, format] imports []
EnvFormat := {} has [
DecoderFormatting {
u8: envU8,
u16: envU16,
u32: envU32,
u64: envU64,
u128: envU128,
i8: envI8,
i16: envI16,
i32: envI32,
i64: envI64,
i128: envI128,
f32: envF32,
f64: envF64,
dec: envDec,
bool: envBool,
string: envString,
list: envList,
record: envRecord,
},
]
format : {} -> EnvFormat
format = \{} -> @EnvFormat {}
decodeBytesToNum = \bytes, transformer ->
when Str.fromUtf8 bytes is
Ok s ->
when transformer s is
Ok n -> { result: Ok n, rest: [] }
Err _ -> { result: Err TooShort, rest: bytes }
Err _ -> { result: Err TooShort, rest: bytes }
envU8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU8
envU16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU16
envU32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU32
envU64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU64
envU128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU128
envI8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI8
envI16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI16
envI32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI32
envI64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI64
envI128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI128
envF32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF32
envF64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF64
envDec = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toDec
envBool = Decode.custom \bytes, @EnvFormat {} ->
when Str.fromUtf8 bytes is
Ok "true" -> { result: Ok Bool.true, rest: [] }
Ok "false" -> { result: Ok Bool.false, rest: [] }
_ -> { result: Err TooShort, rest: bytes }
envString = Decode.custom \bytes, @EnvFormat {} ->
when Str.fromUtf8 bytes is
Ok s -> { result: Ok s, rest: [] }
Err _ -> { result: Err TooShort, rest: bytes }
envList = \decodeElem -> Decode.custom \bytes, @EnvFormat {} ->
# Per our supported methods of decoding, this is either a list of strings or
# a list of numbers; in either case, the list of bytes must be Utf-8
# decodable. So just parse it as a list of strings and pass each chunk to
# the element decoder. By construction, our element decoders expect to parse
# a whole list of bytes anyway.
decodeElems = \allBytes, accum ->
{ toParse, remainder } =
when List.splitFirst allBytes (Num.toU8 ',') is
Ok { before, after } ->
{ toParse: before, remainder: Some after }
Err NotFound ->
{ toParse: allBytes, remainder: None }
when Decode.decodeWith toParse decodeElem (@EnvFormat {}) is
{ result, rest } ->
when result is
Ok val ->
when remainder is
Some restBytes -> decodeElems restBytes (List.append accum val)
None -> Done (List.append accum val)
Err e -> Errored e rest
when decodeElems bytes [] is
Errored e rest -> { result: Err e, rest }
Done vals ->
{ result: Ok vals, rest: [] }
# TODO: we must currently annotate the arrows here so that the lambda sets are
# exercised, and the solver can find an ambient lambda set for the
# specialization.
envRecord : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_ -> _) -> Decoder _ _
envRecord = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} ->
{ result: Err TooShort, rest: bytes }

View file

@ -0,0 +1,25 @@
app "env"
packages { pf: "cli-platform/main.roc" }
imports [pf.Stdout, pf.Env, pf.Task, pf.Program.{ Program }]
provides [main] to pf
main : Program
main =
Env.decode "EDITOR"
|> Task.await (\editor -> Stdout.line "Your favorite editor is \(editor)!")
|> Task.await (\{} -> Env.decode "SHLVL")
|> Task.await
(\lvl ->
when lvl is
1u8 -> Stdout.line "You're running this in a root shell!"
n ->
lvlStr = Num.toStr n
Stdout.line "Your current shell level is \(lvlStr)!")
|> Task.await (\{} -> Env.decode "LETTERS")
|> Task.await
(\letters ->
joinedLetters = Str.joinWith letters " "
Stdout.line "Your favorite letters are: \(joinedLetters)")
|> Program.quick