From 551025d7e0cf17c9c8e0abc040f64fd73c690c77 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 9 Jan 2025 11:19:37 +1100 Subject: [PATCH 01/20] upgrade to snake_case builtins and initial PNC --- Cargo.lock | 46 ++-- build.roc | 178 ++++++------ examples/command.roc | 12 +- examples/dir.roc | 40 +-- examples/echo.roc | 14 +- examples/env.roc | 22 +- examples/error-handling.roc | 42 +-- examples/file-upload-form.roc | 135 ++++----- examples/file.roc | 28 +- examples/hello-web.roc | 18 +- examples/init-basic.roc | 8 +- examples/result.roc | 16 +- examples/sqlite.roc | 55 ++-- examples/temp-dir.roc | 6 +- examples/todos.db-wal | Bin 0 -> 8272 bytes examples/todos.roc | 255 +++++++++-------- flake.lock | 12 +- platform/Cmd.roc | 56 ++-- platform/Dir.roc | 10 +- platform/Env.roc | 46 ++-- platform/EnvDecoding.roc | 129 +++++---- platform/File.roc | 44 +-- platform/FileMetadata.roc | 8 +- platform/Http.roc | 12 +- platform/InternalCmd.roc | 2 +- platform/InternalDateTime.roc | 109 ++++---- platform/InternalHttp.roc | 26 +- platform/InternalIOErr.roc | 2 +- platform/InternalPath.roc | 14 +- platform/MultipartFormData.roc | 481 ++++++++++++++++++--------------- platform/Path.roc | 138 +++++----- platform/SplitList.roc | 74 ++--- platform/Sqlite.roc | 323 ++++++++++++---------- platform/Stderr.roc | 24 +- platform/Stdout.roc | 24 +- platform/Tcp.roc | 56 ++-- platform/Url.roc | 301 +++++++++++---------- platform/Utc.roc | 40 +-- platform/libapp.roc | 15 +- platform/main.roc | 42 +-- 40 files changed, 1516 insertions(+), 1347 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1c7fa1a..1b2c7267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,9 +64,9 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "cc" -version = "1.2.4" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -340,9 +340,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libsqlite3-sys" @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -423,9 +423,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -450,9 +450,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "roc_command" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "roc_io_error", "roc_std", @@ -484,7 +484,7 @@ dependencies = [ [[package]] name = "roc_env" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "roc_file", "roc_std", @@ -494,7 +494,7 @@ dependencies = [ [[package]] name = "roc_file" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "memchr", "roc_io_error", @@ -533,7 +533,7 @@ dependencies = [ [[package]] name = "roc_http" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "bytes", "hyper", @@ -549,7 +549,7 @@ dependencies = [ [[package]] name = "roc_io_error" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "roc_std", "roc_std_heap", @@ -558,7 +558,7 @@ dependencies = [ [[package]] name = "roc_sqlite" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "libsqlite3-sys", "roc_std", @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "roc_std" version = "0.0.1" -source = "git+https://github.com/roc-lang/roc.git#91ed6a5a8e67f4e1a1255fd21f7dca3570e95202" +source = "git+https://github.com/roc-lang/roc.git#fbf448cac882ed9f81cb68242e0091a75f4f202d" dependencies = [ "arrayvec", "static_assertions", @@ -578,7 +578,7 @@ dependencies = [ [[package]] name = "roc_std_heap" version = "0.0.1" -source = "git+https://github.com/roc-lang/roc.git#91ed6a5a8e67f4e1a1255fd21f7dca3570e95202" +source = "git+https://github.com/roc-lang/roc.git#fbf448cac882ed9f81cb68242e0091a75f4f202d" dependencies = [ "memmap2", "roc_std", @@ -587,7 +587,7 @@ dependencies = [ [[package]] name = "roc_stdio" version = "0.0.1" -source = "git+https://github.com/roc-lang/basic-cli.git#71a0666c70950212714e881304c75d95e3f082b7" +source = "git+https://github.com/roc-lang/basic-cli.git#24075b397b52237d99bb665df4c0b4e31ef61629" dependencies = [ "roc_io_error", "roc_std", @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -732,9 +732,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", diff --git a/build.roc b/build.roc index 5bf3261e..3fd3c487 100644 --- a/build.roc +++ b/build.roc @@ -1,117 +1,117 @@ -app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + # TODO replace with snake_case_builtins release + cli: platform "../basic-cli/platform/main.roc", + weaver: "https://github.com/smores56/weaver/releases/download/0.6.0/6WdRio4quZ_3HL8cEY_vyx5mzl1xXrEv2a_c1Bswrq4.tar.br", } import cli.Cmd import cli.Stdout import cli.Env import cli.Arg -import cli.Arg.Opt -import cli.Arg.Cli +import weaver.Opt +import weaver.Cli ## Builds the basic-webserver [platform](https://www.roc-lang.org/platforms). ## ## run with: roc ./build.roc ## -main : Task {} _ -main = - - cli_parser = - Arg.Opt.maybeStr { short: "p", long: "roc", help: "Path to the roc executable. Can be just `roc` or a full path." } - |> Arg.Cli.finish { - name: "basic-webserver-builder", - version: "", - authors: ["Luke Boswell "], - description: "Generates all files needed by Roc to use this basic-cli platform.", - } - |> Arg.Cli.assertValid - - when Arg.Cli.parseOrDisplayMessage cli_parser (Arg.list! {}) is - Ok parsed_args -> run parsed_args - Err err_msg -> Task.err (Exit 1 err_msg) - -run : Result Str err -> Task {} _ -run = \maybe_roc -> +main! : _ => Result {} _ +main! = \args -> + + parsed_args = + Cli.parse_or_display_message(cli_parser, args, Arg.to_os_raw) + |> try Result.on_err! \message -> Err (Exit 1 message) + + run!(parsed_args) + +cli_parser = + Opt.maybe_str { short: "p", long: "roc", help: "Path to the roc executable. Can be just `roc` or a full path." } + |> Cli.finish { + name: "basic-webserver-builder", + version: "", + authors: ["Luke Boswell "], + description: "Generates all files needed by Roc to use this basic-cli platform.", + } + |> Cli.assert_valid + +run! : Result Str err => Result {} _ +run! = \maybe_roc -> # roc_cmd may be a path or just roc - roc_cmd = maybe_roc |> Result.withDefault "roc" + roc_cmd = maybe_roc |> Result.with_default("roc") - roc_version! roc_cmd + roc_version!(roc_cmd)? - os_and_arch = get_os_and_arch! + os_and_arch = get_os_and_arch!({})? - stub_lib_path = "platform/libapp.$(stub_file_extension os_and_arch)" + stub_lib_path = "platform/libapp.$(stub_file_extension(os_and_arch))" - build_stub_app_lib! roc_cmd stub_lib_path + build_stub_app_lib!(roc_cmd, stub_lib_path)? - cargo_build_host! + (cargo_build_host! {})? - rust_target_folder = get_rust_target_folder! + rust_target_folder = get_rust_target_folder!({})? - copy_host_lib! os_and_arch rust_target_folder + copy_host_lib!(os_and_arch, rust_target_folder)? - preprocess_host! roc_cmd stub_lib_path rust_target_folder + preprocess_host!(roc_cmd, stub_lib_path, rust_target_folder)? - info! "Successfully built platform files!" + info!("Successfully built platform files!") -roc_version : Str -> Task {} _ -roc_version = \roc_cmd -> +roc_version! : Str => Result {} _ +roc_version! = \roc_cmd -> - info! "Checking provided roc; executing `$(roc_cmd) version`:" + info!("Checking provided roc; executing `$(roc_cmd) version`:")? - roc_cmd - |> Cmd.exec ["version"] - |> Task.mapErr! RocVersionCheckFailed + Cmd.exec!(roc_cmd, ["version"]) + |> Result.map_err(RocVersionCheckFailed) -get_os_and_arch : Task OSAndArch _ -get_os_and_arch = +get_os_and_arch! : {} => Result OSAndArch _ +get_os_and_arch! = \{} -> - info! "Getting the native operating system and architecture..." + info!("Getting the native operating system and architecture...")? - Env.platform - |> Task.await convert_os_and_arch + convert_os_and_arch(Env.platform!({})) -build_stub_app_lib : Str, Str -> Task {} _ -build_stub_app_lib = \roc_cmd, stub_lib_path -> +build_stub_app_lib! : Str, Str => Result {} _ +build_stub_app_lib! = \roc_cmd, stub_lib_path -> - info! "Building stubbed app shared library ..." + info!("Building stubbed app shared library ...")? - roc_cmd - |> Cmd.exec ["build", "--lib", "platform/libapp.roc", "--output", stub_lib_path, "--optimize"] - |> Task.mapErr! ErrBuildingAppStub - -get_rust_target_folder : Task Str _ -get_rust_target_folder = - when Env.var "CARGO_BUILD_TARGET" |> Task.result! is - Ok target_env_var -> - if Str.isEmpty target_env_var then - Task.ok "target/release/" + Cmd.exec!(roc_cmd, ["build", "--lib", "platform/libapp.roc", "--output", stub_lib_path, "--optimize"]) + |> Result.map_err(ErrBuildingAppStub) + +get_rust_target_folder! : {} => Result Str _ +get_rust_target_folder! = \{} -> + when Env.var!("CARGO_BUILD_TARGET") is + Ok(target_env_var) -> + if Str.is_empty(target_env_var) then + Ok("target/release/") else - Task.ok "target/$(target_env_var)/release/" + Ok("target/$(target_env_var)/release/") - Err e -> - info! "Failed to get env var CARGO_BUILD_TARGET with error $(Inspect.toStr e). Assuming default CARGO_BUILD_TARGET (native)..." + Err(e) -> + info!("Failed to get env var CARGO_BUILD_TARGET with error $(Inspect.to_str(e)). Assuming default CARGO_BUILD_TARGET (native)...")? - Task.ok "target/release/" + Ok("target/release/") -cargo_build_host : Task {} _ -cargo_build_host = +cargo_build_host! : {} => Result {} _ +cargo_build_host! = \{} -> - info! "Building rust host ..." + info!("Building rust host ...")? - "cargo" - |> Cmd.exec ["build", "--release"] - |> Task.mapErr! ErrBuildingHostBinaries + Cmd.exec!("cargo", ["build", "--release"]) + |> Result.map_err(ErrBuildingHostBinaries) -copy_host_lib : OSAndArch, Str -> Task {} _ -copy_host_lib = \os_and_arch, rust_target_folder -> +copy_host_lib! : OSAndArch, Str => Result {} _ +copy_host_lib! = \os_and_arch, rust_target_folder -> host_build_path = "$(rust_target_folder)libhost.a" - host_dest_path = "platform/$(prebuilt_static_lib_file os_and_arch)" + host_dest_path = "platform/$(prebuilt_static_lib_file(os_and_arch))" + + info!("Moving the prebuilt binary from $(host_build_path) to $(host_dest_path) ...")? - info! "Moving the prebuilt binary from $(host_build_path) to $(host_dest_path) ..." - "cp" - |> Cmd.exec [host_build_path, host_dest_path] - |> Task.mapErr! ErrMovingPrebuiltLegacyBinary + Cmd.exec!("cp", [host_build_path, host_dest_path]) + |> Result.map_err(ErrMovingPrebuiltLegacyBinary) OSAndArch : [ MacosArm64, @@ -122,14 +122,14 @@ OSAndArch : [ WindowsX64, ] -convert_os_and_arch : _ -> Task OSAndArch _ +convert_os_and_arch : _ -> Result OSAndArch _ convert_os_and_arch = \{ os, arch } -> when (os, arch) is - (MACOS, AARCH64) -> Task.ok MacosArm64 - (MACOS, X64) -> Task.ok MacosX64 - (LINUX, AARCH64) -> Task.ok LinuxArm64 - (LINUX, X64) -> Task.ok LinuxX64 - _ -> Task.err (UnsupportedNative os arch) + (MACOS, AARCH64) -> Ok(MacosArm64) + (MACOS, X64) -> Ok(MacosX64) + (LINUX, AARCH64) -> Ok(LinuxArm64) + (LINUX, X64) -> Ok(LinuxX64) + _ -> Err(UnsupportedNative(os, arch)) stub_file_extension : OSAndArch -> Str stub_file_extension = \os_and_arch -> @@ -148,15 +148,17 @@ prebuilt_static_lib_file = \os_and_arch -> WindowsArm64 -> "windows-arm64.lib" WindowsX64 -> "windows-x64.lib" -preprocess_host : Str, Str, Str -> Task {} _ -preprocess_host = \roc_cmd, stub_lib_path, rust_target_folder -> - info! "Preprocessing surgical host ..." +preprocess_host! : Str, Str, Str => Result {} _ +preprocess_host! = \roc_cmd, stub_lib_path, rust_target_folder -> + + info!("Preprocessing surgical host ...")? + surgical_build_path = "$(rust_target_folder)host" roc_cmd - |> Cmd.exec ["preprocess-host", surgical_build_path, "platform/main.roc", stub_lib_path] - |> Task.mapErr! ErrPreprocessingSurgicalBinary + |> Cmd.exec!(["preprocess-host", surgical_build_path, "platform/main.roc", stub_lib_path]) + |> Result.map_err(ErrPreprocessingSurgicalBinary) -info : Str -> Task {} _ -info = \msg -> - Stdout.line! "\u(001b)[34mINFO:\u(001b)[0m $(msg)" +info! : Str => Result {} _ +info! = \msg -> + Stdout.line!("\u(001b)[34mINFO:\u(001b)[0m $(msg)") diff --git a/examples/command.roc b/examples/command.roc index 62d81be0..ec026e18 100644 --- a/examples/command.roc +++ b/examples/command.roc @@ -7,18 +7,18 @@ import pf.Utc Model : {} init! : {} => Result Model _ -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [CmdStatusErr _] respond! = \req, _ -> # Log request date, method and url using echo program - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - try Cmd.exec! "echo" ["$(datetime) $(Inspect.toStr req.method) $(req.uri)"] + Cmd.exec!("echo", ["$(datetime) $(Inspect.to_str(req.method)) $(req.uri)"])? - Ok { + Ok({ status: 200, headers: [], - body: Str.toUtf8 "Command succeeded.", - } + body: Str.to_utf8("Command succeeded."), + }) diff --git a/examples/dir.roc b/examples/dir.roc index 108749e5..df144e8b 100644 --- a/examples/dir.roc +++ b/examples/dir.roc @@ -12,35 +12,43 @@ init! : {} => Result Model _ init! = \{} -> # Get current working directory cwd = - Env.cwd! {} - |> Result.mapErr? \CwdUnavailable -> Exit 1 "Unable to read current working directory" + Result.map_err( + Env.cwd!({}), + \CwdUnavailable -> Exit(1, "Unable to read current working directory"), + )? - try Stdout.line! "The current working directory is $(Path.display cwd)" + Stdout.line!("The current working directory is $(Path.display(cwd))")? # Try to set cwd to examples - Env.set_cwd! (Path.from_str "examples/") - |> Result.mapErr? \InvalidCwd -> Exit 1 "Unable to set cwd to examples/" + Result.map_err( + Env.set_cwd!(Path.from_str("examples/")), + \InvalidCwd -> Exit(1, "Unable to set cwd to examples/"), + )? - try Stdout.line! "Set cwd to examples/" + Stdout.line!("Set cwd to examples/")? # List contents of examples directory paths = - Dir.list! "./" - |> Result.mapErr? \DirErr err -> Exit 1 "Error reading directory ./:\n\t$(Inspect.toStr err)" + Result.map_err( + Dir.list!("./"), + \DirErr(err) -> Exit(1, "Error reading directory ./:\n\t$(Inspect.to_str(err))"), + )? paths - |> List.map Path.display - |> Str.joinWith "," + |> List.map(Path.display) + |> Str.join_with(",") |> \paths_str -> "The paths are;\n$(paths_str)" |> Stdout.line! |> try - Ok {} + Ok({}) respond! : Request, Model => Result Response [] respond! = \_, _ -> - Ok { - status: 200, - headers: [], - body: Str.toUtf8 "Logged request", - } + Ok( + { + status: 200, + headers: [], + body: Str.to_utf8("Logged request"), + }, + ) diff --git a/examples/echo.roc b/examples/echo.roc index 11c33711..b1c25f99 100644 --- a/examples/echo.roc +++ b/examples/echo.roc @@ -7,19 +7,19 @@ import pf.Utc Model : {} init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [StdoutErr _] respond! = \req, _ -> # Log request datetime, method and url - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - try Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" + Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)")? # Respond with request body - if List.isEmpty req.body then - success [] + if List.is_empty(req.body) then + success([]) else - success req.body + success(req.body) -success = \body -> Ok { status: 200, headers: [], body } +success = \body -> Ok({ status: 200, headers: [], body }) diff --git a/examples/env.roc b/examples/env.roc index c24671d3..5de373e2 100644 --- a/examples/env.roc +++ b/examples/env.roc @@ -10,9 +10,9 @@ init! : {} => Result Model [Exit I32 Str]_ init! = \{} -> # Check if DEBUG environment variable is set - when Env.var! "DEBUG" is - Ok var if !(Str.isEmpty var) -> Ok DebugPrintMode - _ -> Ok NonDebugMode + when Env.var!("DEBUG") is + Ok(var) if !(Str.is_empty(var)) -> Ok(DebugPrintMode) + _ -> Ok(NonDebugMode) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \_, debug -> @@ -20,19 +20,19 @@ respond! = \_, debug -> DebugPrintMode -> # Respond with all the current environment variables vars : Dict Str Str - vars = Env.dict! {} + vars = Env.dict!({}) # Convert the Dict to a list of key-value pairs body = vars - |> Dict.toList - |> List.map \(k, v) -> "$(k): $(v)" - |> Str.joinWith "\n" - |> Str.concat "\n" - |> Str.toUtf8 + |> Dict.to_list + |> List.map(\(k, v) -> "$(k): $(v)") + |> Str.join_with("\n") + |> Str.concat("\n") + |> Str.to_utf8 - Ok { status: 200, headers: [], body } + Ok({ status: 200, headers: [], body }) NonDebugMode -> # Respond with a message that DEBUG is not set - Ok { status: 200, headers: [], body: Str.toUtf8 "DEBUG var not set" } + Ok({ status: 200, headers: [], body: Str.to_utf8("DEBUG var not set") }) diff --git a/examples/error-handling.roc b/examples/error-handling.roc index 636b4c63..9884db7c 100644 --- a/examples/error-handling.roc +++ b/examples/error-handling.roc @@ -9,10 +9,10 @@ import pf.Env Model : {} init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ -respond! = \req, _ -> handle_req! req |> Result.mapErr map_app_err +respond! = \req, _ -> handle_req!(req) |> Result.map_err(map_app_err) AppError : [ EnvVarNotSet Str, @@ -23,46 +23,48 @@ AppError : [ map_app_err : AppError -> [ServerErr Str] map_app_err = \app_err -> when app_err is - EnvVarNotSet env_var_name -> ServerErr "Environment variable \"$(env_var_name)\" was not set." - BadBody err -> ServerErr "Http error fetching content:\n\t$(Inspect.toStr err)" - StdoutErr err -> ServerErr "Stdout error logging request:\n\t$(err)" + EnvVarNotSet(env_var_name) -> ServerErr("Environment variable \"$(env_var_name)\" was not set.") + BadBody(err) -> ServerErr("Http error fetching content:\n\t$(Inspect.to_str(err))") + StdoutErr(err) -> ServerErr("Stdout error logging request:\n\t$(err)") # Here we use AppError to ensure all errors must be handled within our application. handle_req! : Request => Result Response AppError handle_req! = \req -> # Log the date, time, method, and url to stdout - try log_request! req + log_request!(req)? # Read environment variable - url = try read_env_var! "TARGET_URL" + url = read_env_var!("TARGET_URL")? # Fetch content of url - content = try fetch_content! url + content = fetch_content!(url)? # Respond with the website content - response_with_code! 200 content + response_with_code!(200, content) log_request! : Request => Result {} [StdoutErr Str] log_request! = \req -> - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" - |> Result.mapErr \err -> StdoutErr (Inspect.toStr err) + Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)") + |> Result.map_err(\err -> StdoutErr(Inspect.to_str(err))) read_env_var! : Str => Result Str [EnvVarNotSet Str] read_env_var! = \env_var_name -> - Env.var! env_var_name - |> Result.mapErr \_ -> EnvVarNotSet env_var_name + Env.var!(env_var_name) + |> Result.map_err(\_ -> EnvVarNotSet(env_var_name)) fetch_content! : Str => Result Str _ fetch_content! = \url -> - Http.get_utf8! url + Http.get_utf8!(url) # Respond with the given status code and body response_with_code! : U16, Str => Result Response * response_with_code! = \code, body -> - Ok { - status: code, - headers: [], - body: Str.toUtf8 body, - } + Ok( + { + status: code, + headers: [], + body: Str.to_utf8(body), + }, + ) diff --git a/examples/file-upload-form.roc b/examples/file-upload-form.roc index 81641266..cd63343f 100644 --- a/examples/file-upload-form.roc +++ b/examples/file-upload-form.roc @@ -1,6 +1,8 @@ app [Model, init!, respond!] { pf: platform "../platform/main.roc", - utils: "https://github.com/quelgar/roc-utils/releases/download/v0.1.0/keYHFjUG1pMAT8ECePEAIS-ncYxEV0DdhTvENUf0USs.tar.br", + + # TODO replace with roc-utils package release compatible with snake_case_builtins + utils: "../../roc-utils/package/main.roc", } import utils.Base64 @@ -11,84 +13,87 @@ import pf.MultipartFormData Model : {} init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \req, _ -> if req.method == GET then - body = - """ - - - - Image Upload Form - - - -

Upload an Image

+ Ok( + { + status: 200, + headers: [ + { name: "Content-Type", value: "text/html" }, + ], + body: Str.to_utf8( + """ + + + + Image Upload Form + + -
-

-

- -
+

Upload an Image

- - - """ - |> Str.toUtf8 +
+

+

+ +
- Ok { - status: 200, - headers: [ - { name: "Content-Type", value: "text/html" }, - ], - body, - } + + + """, + ), + }, + ) else if req.method == POST then page = \src -> - """ - - - - - Embedded Image - - - - -

You uploaded:

-
- - - """ - |> Str.toUtf8 + Str.to_utf8( + """ + + + + + Embedded Image + + + + +

You uploaded:

+
+ + + """, + ) maybe_image = { headers: req.headers, body: req.body } |> MultipartFormData.parse_multipart_form_data - |> Result.try List.first - |> Result.map .data - |> Result.map Base64.encode + |> Result.try(List.first) + |> Result.map(.data) + |> Result.map(Base64.encode) when maybe_image is - Ok img -> - Ok { - status: 200, - headers: [ - { name: "Content-Type", value: "text/html" }, - ], - body: page img, - } + Ok(img) -> + Ok( + { + status: 200, + headers: [ + { name: "Content-Type", value: "text/html" }, + ], + body: page(img), + }, + ) - Err err -> Ok { status: 500, headers: [], body: err |> Inspect.toStr |> Str.toUtf8 } + Err(err) -> Ok({ status: 500, headers: [], body: Str.to_utf8(Inspect.to_str(err)) }) else - Ok { status: 500, headers: [], body: [] } + Ok({ status: 500, headers: [], body: [] }) diff --git a/examples/file.roc b/examples/file.roc index ec162e30..f396e54d 100644 --- a/examples/file.roc +++ b/examples/file.roc @@ -11,19 +11,25 @@ Model : Str init! : {} => Result Model [Exit I32 Str]_ init! = \{} -> # Read the contents of examples/file.roc - when File.read_utf8! "examples/file.roc" is - Ok contents -> - Ok "Source code of current program:\n\n$(contents)" + File.read_utf8!("examples/file.roc") + |> Result.map(\contents -> "Source code of current program:\n\n$(contents)") + |> Result.map_err( + \err -> + when err is + FileReadErr(path, file_err) -> + Exit( + -1, + "Failed to launch server!\nError reading file $(Path.display(path)):\n\t$(Inspect.to_str(file_err))", + ) - Err (FileReadErr path err) -> - Exit -1 "Failed to launch server!\nError reading file $(Path.display path):\n\t$(Inspect.toStr err)" - |> Err - - Err (FileReadUtf8Err path _) -> - Exit -2 "Failed to launch server!\nError: failed to read file $(Path.display path) as utf8." - |> Err + FileReadUtf8Err(path, _) -> + Exit( + -2, + "Failed to launch server!\nError: failed to read file $(Path.display(path)) as utf8.", + ), + ) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \_, model -> # If the server launched, the model contains the file content. - Ok { status: 200, headers: [], body: Str.toUtf8 model } + Ok({ status: 200, headers: [], body: Str.to_utf8(model) }) diff --git a/examples/hello-web.roc b/examples/hello-web.roc index 3b5926aa..d08f2c4e 100644 --- a/examples/hello-web.roc +++ b/examples/hello-web.roc @@ -11,17 +11,19 @@ Model : {} # generate css by running `tailwindcss`,... # In this case we don't have anything to initialize, so it is just `Ok {}`. init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \req, _ -> # Log request datetime, method and url - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - try Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" + Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)")? - Ok { - status: 200, - headers: [], - body: Str.toUtf8 "Hello from server
", - } + Ok( + { + status: 200, + headers: [], + body: Str.to_utf8("Hello from server
"), + }, + ) diff --git a/examples/init-basic.roc b/examples/init-basic.roc index 38028edf..99558550 100644 --- a/examples/init-basic.roc +++ b/examples/init-basic.roc @@ -11,13 +11,13 @@ Model : Str # generate css by running `tailwindcss`,... # In this example it is just `Ok "🎁"`. init! : {} => Result Model [] -init! = \{} -> Ok "🎁" +init! = \{} -> Ok("🎁") respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \req, model -> # Log request datetime, method and url - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - try Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" + Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)")? - Ok { status: 200, headers: [], body: Str.toUtf8 "init gave me $(model)" } + Ok({ status: 200, headers: [], body: Str.to_utf8("init gave me $(model)") }) diff --git a/examples/result.roc b/examples/result.roc index f1e63ab8..5290ae04 100644 --- a/examples/result.roc +++ b/examples/result.roc @@ -5,14 +5,14 @@ import pf.Http exposing [Request, Response] Model : {} init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \_, _ -> - when check_file! "good" is - Ok Good -> Ok { status: 200, headers: [], body: Str.toUtf8 "GOOD" } - Ok Bad -> Ok { status: 200, headers: [], body: Str.toUtf8 "BAD" } - Err IOError -> Ok { status: 500, headers: [], body: Str.toUtf8 "ERROR: IoError when executing checkFile!." } + when check_file!("good") is + Ok(Good) -> Ok({ status: 200, headers: [], body: Str.to_utf8("GOOD") }) + Ok(Bad) -> Ok({ status: 200, headers: [], body: Str.to_utf8("BAD") }) + Err(IOError) -> Ok({ status: 500, headers: [], body: Str.to_utf8("ERROR: IoError when executing checkFile!.") }) # imagine this function does some IO operation # and returns a Result, succeding with a tag either Good or Bad, @@ -20,8 +20,8 @@ respond! = \_, _ -> check_file! : Str => Result [Good, Bad] [IOError] check_file! = \str -> if str == "good" then - Ok Good + Ok(Good) else if str == "bad" then - Ok Bad + Ok(Bad) else - Err IOError + Err(IOError) diff --git a/examples/sqlite.roc b/examples/sqlite.roc index effed496..afcaae4c 100644 --- a/examples/sqlite.roc +++ b/examples/sqlite.roc @@ -11,42 +11,47 @@ init! : {} => Result Model _ init! = \{} -> # Read DB_PATH environment variable db_path = - Env.var! "DB_PATH" - |> Result.mapErr \_ -> ServerErr "DB_PATH not set on environment" + Env.var!("DB_PATH") + |> Result.map_err(\_ -> ServerErr("DB_PATH not set on environment")) |> try stmt = - Sqlite.prepare! { - path: db_path, - query: "SELECT id, task FROM todos WHERE status = :status;", - } - |> Result.mapErr \err -> ServerErr "Failed to prepare Sqlite statement: $(Inspect.toStr err)" + Sqlite.prepare!( + { + path: db_path, + query: "SELECT id, task FROM todos WHERE status = :status;", + }, + ) + |> Result.map_err(\err -> ServerErr("Failed to prepare Sqlite statement: $(Inspect.to_str(err))")) |> try - Ok { stmt } + Ok({ stmt }) respond! : Request, Model => Result Response _ respond! = \_, { stmt } -> # Query todos table strings : Str strings = - Sqlite.query_many_prepared! { - stmt, - bindings: [{ name: ":status", value: String "completed" }], - rows: { Sqlite.decode_record <- - id: Sqlite.i64 "id", - task: Sqlite.str "task", + Sqlite.query_many_prepared!( + { + stmt, + bindings: [{ name: ":status", value: String("completed") }], + rows: { Sqlite.decode_record <- + id: Sqlite.i64("id"), + task: Sqlite.str("task"), + }, }, - } - |> try - |> List.map \{ id, task } -> "row $(Num.toStr id), task: $(task)" - |> Str.joinWith "\n" + )? + |> List.map(\{ id, task } -> "row $(Num.to_str(id)), task: $(task)") + |> Str.join_with("\n") # Print out the results - try Stdout.line! strings - - Ok { - status: 200, - headers: [{ name: "Content-Type", value: "text/html; charset=utf-8" }], - body: Str.toUtf8 strings, - } + Stdout.line!(strings)? + + Ok( + { + status: 200, + headers: [{ name: "Content-Type", value: "text/html; charset=utf-8" }], + body: Str.to_utf8(strings), + }, + ) diff --git a/examples/temp-dir.roc b/examples/temp-dir.roc index b9d002d5..8e6fea53 100644 --- a/examples/temp-dir.roc +++ b/examples/temp-dir.roc @@ -12,11 +12,11 @@ import pf.Env Model : {} init! : {} => Result Model [] -init! = \{} -> Ok {} +init! = \{} -> Ok({}) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \_, _ -> - temp_dir_str = Path.display (Env.temp_dir! {}) + temp_dir_str = Path.display(Env.temp_dir!({})) - Ok { status: 200, headers: [], body: Str.toUtf8 "The temp dir path is $(temp_dir_str)" } + Ok({ status: 200, headers: [], body: Str.to_utf8("The temp dir path is $(temp_dir_str)") }) diff --git a/examples/todos.db-wal b/examples/todos.db-wal index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..40e264b046eab9fda35affd466f5f620a83453a1 100644 GIT binary patch literal 8272 zcmXr7XKP~6eI&uaAiw|uOClF+x;F2YQhR&M_9V~CMnEAZWN{8@wsoo9OsjZ-g8Y1I z8Tha9Z|0xKU&o)#AHna+Z^SRp&%^hV?-}1^z5_t%Q7{?;qaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UpkS0YQEi21)6V#NuoPvx1z&yu8#DK|V|&UQ8h#Od)PeAudcI zPD~*VOd)nmAvR1QR!kulxR5E-Lki3+47Q@d8Hq)y3b~aEkLE6UzoTD4AuYd1At*n& zBtIozk%@)DP&qs^Pa!cUN1-GmRUx<}v8Y%vGf%gmC_lX@wYXS@k%d89KAPHIVN3R;9Q1NqFb@q(j&H}c+H#AgW_7hvRn$pA#7U^E0qLtr!nMnhmU r1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(sD}V2BeOUoXjq|`AK5Da7Cwj9 literal 0 HcmV?d00001 diff --git a/examples/todos.roc b/examples/todos.roc index 419221d3..24f0cc6b 100644 --- a/examples/todos.roc +++ b/examples/todos.roc @@ -7,7 +7,7 @@ import pf.Sqlite import pf.Stdout import pf.Url import pf.Utc -import "todos.html" as todoHtml : List U8 +import "todos.html" as todo_html : List U8 Model : { list_todos_stmt : Sqlite.Stmt, @@ -20,36 +20,36 @@ Model : { init! : {} => Result Model [Exit I32 Str]_ init! = \{} -> - db_path = try read_env_var! "DB_PATH" + db_path = read_env_var!("DB_PATH")? - list_todos_stmt = try prepare_stmt! db_path "SELECT id, task, status FROM todos" - create_todo_stmt = try prepare_stmt! db_path "INSERT INTO todos (task, status) VALUES (:task, :status)" - last_created_todo_stmt = try prepare_stmt! db_path "SELECT id, task, status FROM todos WHERE id = last_insert_rowid()" - begin_stmt = try prepare_stmt! db_path "BEGIN" - end_stmt = try prepare_stmt! db_path "END" - rollback_stmt = try prepare_stmt! db_path "ROLLBACK" + list_todos_stmt = prepare_stmt!(db_path, "SELECT id, task, status FROM todos")? + create_todo_stmt = prepare_stmt!(db_path, "INSERT INTO todos (task, status) VALUES (:task, :status)")? + last_created_todo_stmt = prepare_stmt!(db_path, "SELECT id, task, status FROM todos WHERE id = last_insert_rowid()")? + begin_stmt = prepare_stmt!(db_path, "BEGIN")? + end_stmt = prepare_stmt!(db_path, "END")? + rollback_stmt = prepare_stmt!(db_path, "ROLLBACK")? - Ok { list_todos_stmt, create_todo_stmt, last_created_todo_stmt, begin_stmt, end_stmt, rollback_stmt } + Ok({ list_todos_stmt, create_todo_stmt, last_created_todo_stmt, begin_stmt, end_stmt, rollback_stmt }) respond! : Request, Model => Result Response [ServerErr Str]_ respond! = \req, model -> response_task = - try log_request! req + log_request!(req)? split_url = req.uri |> Url.from_str |> Url.path - |> Str.splitOn "/" + |> Str.split_on("/") # Route to handler based on url path when split_url is - ["", ""] -> byte_response 200 todoHtml - ["", "todos", ..] -> route_todos! model req - _ -> text_response 404 "URL Not Found (404)" + ["", ""] -> byte_response(200, todo_html) + ["", "todos", ..] -> route_todos!(model, req) + _ -> text_response(404, "URL Not Found (404)") # Handle any application errors - response_task |> Result.mapErr map_app_err + response_task |> Result.map_err(map_app_err) AppError : [ EnvVarNotSet Str, @@ -59,181 +59,208 @@ AppError : [ map_app_err : AppError -> [ServerErr Str] map_app_err = \app_err -> when app_err is - EnvVarNotSet var_name -> ServerErr "Environment variable \"$(var_name)\" was not set. Please set it to the path of todos.db" - StdoutErr msg -> ServerErr msg + EnvVarNotSet(var_name) -> ServerErr("Environment variable \"$(var_name)\" was not set. Please set it to the path of todos.db") + StdoutErr(msg) -> ServerErr(msg) route_todos! : Model, Request => Result Response _ route_todos! = \model, req -> when req.method is GET -> - list_todos! model + list_todos!(model) POST -> # Create todo - when task_from_query req.uri is - Ok props -> create_todo! model props - Err InvalidQuery -> text_response 400 "Invalid query string, I expected: ?task=foo&status=bar" + when task_from_query(req.uri) is + Ok(props) -> create_todo!(model, props) + Err(InvalidQuery) -> text_response(400, "Invalid query string, I expected: ?task=foo&status=bar") other_method -> # Not supported - text_response 405 "HTTP method $(Inspect.toStr other_method) is not supported for the URL $(req.uri)" + text_response(405, "HTTP method $(Inspect.to_str(other_method)) is not supported for the URL $(req.uri)") list_todos! : Model => Result Response _ list_todos! = \{ list_todos_stmt } -> result = # TODO: it might be nicer if the decoder was stored with the prepared query instead of defined here. - Sqlite.query_many_prepared! { - stmt: list_todos_stmt, - bindings: [], - rows: { Sqlite.decode_record <- - id: Sqlite.i64 "id", - task: Sqlite.str "task", - status: Sqlite.str "status", + Sqlite.query_many_prepared!( + { + stmt: list_todos_stmt, + bindings: [], + rows: { Sqlite.decode_record <- + id: Sqlite.i64("id"), + task: Sqlite.str("task"), + status: Sqlite.str("status"), + }, }, - } + ) + when result is - Ok task -> + Ok(task) -> task - |> List.map encode_task - |> Str.joinWith "," + |> List.map(encode_task) + |> Str.join_with(",") |> \list -> "[$(list)]" - |> Str.toUtf8 + |> Str.to_utf8 |> json_response - Err err -> - err_response err + Err(err) -> + err_response(err) create_todo! : Model, { task : Str, status : Str } => Result Response _ create_todo! = \model, params -> result = - exec_transaction! model \{} -> - try Sqlite.execute_prepared! { - stmt: model.create_todo_stmt, - bindings: [ - { name: ":task", value: String params.task }, - { name: ":status", value: String params.status }, - ], - } - Sqlite.query_prepared! { - stmt: model.last_created_todo_stmt, - bindings: [], - row: { Sqlite.decode_record <- - id: Sqlite.i64 "id", - task: Sqlite.str "task", - status: Sqlite.str "status", - }, - } + exec_transaction!( + model, + \{} -> + # prepare create todo statement + Sqlite.execute_prepared!( + { + stmt: model.create_todo_stmt, + bindings: [ + { name: ":task", value: String(params.task) }, + { name: ":status", value: String(params.status) }, + ], + }, + )? + + # prepate last created todo statement + Sqlite.query_prepared!( + { + stmt: model.last_created_todo_stmt, + bindings: [], + row: { Sqlite.decode_record <- + id: Sqlite.i64("id"), + task: Sqlite.str("task"), + status: Sqlite.str("status"), + }, + }, + ), + ) when result is - Ok task -> + Ok(task) -> task |> encode_task - |> Str.toUtf8 + |> Str.to_utf8 |> json_response - Err err -> - err_response err + Err(err) -> + err_response(err) exec_transaction! : Model, ({} => Result ok err) => Result ok [FailedToBeginTransaction, FailedToEndTransaction, FailedToRollbackTransaction, TransactionFailed err] exec_transaction! = \{ begin_stmt, rollback_stmt, end_stmt }, transaction! -> # TODO: create a nicer transaction wrapper - Sqlite.execute_prepared! { - stmt: begin_stmt, - bindings: [], - } - |> Result.mapErr \_ -> FailedToBeginTransaction + Sqlite.execute_prepared!( + { + stmt: begin_stmt, + bindings: [], + }, + ) + |> Result.map_err(\_ -> FailedToBeginTransaction) |> try end_transaction! = \res -> when res is - Ok v -> - Sqlite.execute_prepared! { - stmt: end_stmt, - bindings: [], - } - |> Result.mapErr \_ -> FailedToEndTransaction + Ok(v) -> + Sqlite.execute_prepared!( + { + stmt: end_stmt, + bindings: [], + }, + ) + |> Result.map_err(\_ -> FailedToEndTransaction) |> try - Ok v - Err e -> - Err (TransactionFailed e) + Ok(v) - when transaction! {} |> end_transaction! is - Ok v -> - Ok v + Err(e) -> + Err(TransactionFailed(e)) - Err e -> - Sqlite.execute_prepared! { - stmt: rollback_stmt, - bindings: [], - } - |> Result.mapErr \_ -> FailedToRollbackTransaction + when transaction!({}) |> end_transaction! is + Ok(v) -> + Ok(v) + + Err(e) -> + Sqlite.execute_prepared!( + { + stmt: rollback_stmt, + bindings: [], + }, + ) + |> Result.map_err(\_ -> FailedToRollbackTransaction) |> try - Err e + Err(e) task_from_query : Str -> Result { task : Str, status : Str } [InvalidQuery] task_from_query = \url -> + params = url |> Url.from_str |> Url.query_params - when (params |> Dict.get "task", params |> Dict.get "status") is - (Ok task, Ok status) -> Ok { task: Str.replaceEach task "%20" " ", status: Str.replaceEach status "%20" " " } - _ -> Err InvalidQuery + when (params |> Dict.get("task"), params |> Dict.get("status")) is + (Ok(task), Ok(status)) -> Ok({ task: Str.replace_each(task, "%20", " "), status: Str.replace_each(status, "%20", " ") }) + _ -> Err(InvalidQuery) encode_task : { id : I64, task : Str, status : Str } -> Str encode_task = \{ id, task, status } -> # TODO: this should use our json encoder """ - {"id":$(Num.toStr id),"task":"$(task)","status":"$(status)"} + {"id":$(Num.to_str(id)),"task":"$(task)","status":"$(status)"} """ json_response : List U8 -> Result Response [] json_response = \bytes -> - Ok { - status: 200, - headers: [ - { name: "Content-Type", value: "application/json; charset=utf-8" }, - ], - body: bytes, - } + Ok( + { + status: 200, + headers: [ + { name: "Content-Type", value: "application/json; charset=utf-8" }, + ], + body: bytes, + }, + ) err_response : err -> Result Response * where err implements Inspect err_response = \err -> - byte_response 500 (Str.toUtf8 (Inspect.toStr err)) + byte_response(500, Str.to_utf8(Inspect.to_str(err))) text_response : U16, Str -> Result Response [] text_response = \status, str -> - Ok { - status, - headers: [ - { name: "Content-Type", value: "text/html; charset=utf-8" }, - ], - body: Str.toUtf8 str, - } + Ok( + { + status, + headers: [ + { name: "Content-Type", value: "text/html; charset=utf-8" }, + ], + body: Str.to_utf8(str), + }, + ) byte_response : U16, List U8 -> Result Response * byte_response = \status, bytes -> - Ok { - status, - headers: [ - { name: "Content-Type", value: "text/html; charset=utf-8" }, - ], - body: bytes, - } + Ok( + { + status, + headers: [ + { name: "Content-Type", value: "text/html; charset=utf-8" }, + ], + body: bytes, + }, + ) log_request! : Request => Result {} [StdoutErr Str] log_request! = \req -> - datetime = Utc.to_iso_8601 (Utc.now! {}) + datetime = Utc.to_iso_8601(Utc.now!({})) - Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" - |> Result.mapErr \err -> StdoutErr (Inspect.toStr err) + Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)") + |> Result.map_err(\err -> StdoutErr(Inspect.to_str(err))) read_env_var! : Str => Result Str [EnvVarNotSet Str]_ read_env_var! = \env_var_name -> - Env.var! env_var_name - |> Result.mapErr \_ -> EnvVarNotSet env_var_name + Env.var!(env_var_name) + |> Result.map_err(\_ -> EnvVarNotSet(env_var_name)) prepare_stmt! : Str, Str => Result Sqlite.Stmt [FailedToPrepareQuery _] prepare_stmt! = \path, query -> - Sqlite.prepare! { path, query } - |> Result.mapErr FailedToPrepareQuery + Sqlite.prepare!({ path, query }) + |> Result.map_err(FailedToPrepareQuery) diff --git a/flake.lock b/flake.lock index fdc58e22..0990e2a4 100644 --- a/flake.lock +++ b/flake.lock @@ -102,11 +102,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1735379823, - "narHash": "sha256-HVcLUsA9YhV7UVpRuZkgq1v4MLnZ1/ppjF5c9WmsVLs=", + "lastModified": 1736378239, + "narHash": "sha256-J7/bHTiRAefX/czxRkTbik/iMaJDL5ZmTnMfXJE+/nY=", "owner": "roc-lang", "repo": "roc", - "rev": "9b13b19d08523982d11178cce45ed625ae1f9e0e", + "rev": "fbf448cac882ed9f81cb68242e0091a75f4f202d", "type": "github" }, "original": { @@ -154,11 +154,11 @@ ] }, "locked": { - "lastModified": 1735352767, - "narHash": "sha256-3zXufMRWUdwmp8/BTmxVW/k4MyqsPjLnnt/IlQyZvhc=", + "lastModified": 1736303309, + "narHash": "sha256-IKrk7RL+Q/2NC6+Ql6dwwCNZI6T6JH2grTdJaVWHF0A=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a16b9a7cac7f4d39a84234d62e91890370c57d76", + "rev": "a0b81d4fa349d9af1765b0f0b4a899c13776f706", "type": "github" }, "original": { diff --git a/platform/Cmd.roc b/platform/Cmd.roc index 109cd66d..20cdb128 100644 --- a/platform/Cmd.roc +++ b/platform/Cmd.roc @@ -25,12 +25,14 @@ Output : InternalCmd.Output ## Create a new command to execute the given program in a child process. new : Str -> Cmd new = \program -> - @Cmd { - program, - args: [], - envs: [], - clear_envs: Bool.false, - } + @Cmd( + { + program, + args: [], + envs: [], + clear_envs: Bool.false, + }, + ) ## Add a single argument to the command. ## ! Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. @@ -42,8 +44,8 @@ new = \program -> ## ``` ## arg : Cmd, Str -> Cmd -arg = \@Cmd cmd, value -> - @Cmd ({ cmd & args: List.append cmd.args value }) +arg = \@Cmd(cmd), value -> + @Cmd({ cmd & args: List.append(cmd.args, value) }) ## Add multiple arguments to the command. ## ! Shell features like variable subsitition (e.g. `$FOO`), glob patterns (e.g. `*.txt`), ... are not available. @@ -55,8 +57,8 @@ arg = \@Cmd cmd, value -> ## ``` ## args : Cmd, List Str -> Cmd -args = \@Cmd cmd, values -> - @Cmd ({ cmd & args: List.concat cmd.args values }) +args = \@Cmd(cmd), values -> + @Cmd({ cmd & args: List.concat(cmd.args, values) }) ## Add a single environment variable to the command. ## @@ -67,8 +69,8 @@ args = \@Cmd cmd, values -> ## ``` ## env : Cmd, Str, Str -> Cmd -env = \@Cmd cmd, key, value -> - @Cmd ({ cmd & envs: List.concat cmd.envs [key, value] }) +env = \@Cmd(cmd), key, value -> + @Cmd({ cmd & envs: List.concat(cmd.envs, [key, value]) }) ## Add multiple environment variables to the command. ## @@ -79,9 +81,9 @@ env = \@Cmd cmd, key, value -> ## ``` ## envs : Cmd, List (Str, Str) -> Cmd -envs = \@Cmd cmd, key_values -> - values = key_values |> List.joinMap \(key, value) -> [key, value] - @Cmd { cmd & envs: List.concat cmd.envs values } +envs = \@Cmd(cmd), key_values -> + values = key_values |> List.join_map(\(key, value) -> [key, value]) + @Cmd({ cmd & envs: List.concat(cmd.envs, values) }) ## Clear all environment variables, and prevent inheriting from parent, only ## the environment variables provided to command are available to the child. @@ -94,8 +96,8 @@ envs = \@Cmd cmd, key_values -> ## ``` ## clear_envs : Cmd -> Cmd -clear_envs = \@Cmd cmd -> - @Cmd { cmd & clear_envs: Bool.true } +clear_envs = \@Cmd(cmd) -> + @Cmd({ cmd & clear_envs: Bool.true }) ## Execute command and capture stdout and stderr ## @@ -103,17 +105,17 @@ clear_envs = \@Cmd cmd -> ## > to read from the stdin stream will result in the stream immediately closing. ## output! : Cmd => Output -output! = \@Cmd cmd -> - Host.command_output! cmd +output! = \@Cmd(cmd) -> + Host.command_output!(cmd) |> InternalCmd.from_host_output ## Execute command and inherit stdin, stdout and stderr from parent ## status! : Cmd => Result I32 [CmdStatusErr InternalIOErr.IOErr] -status! = \@Cmd cmd -> - Host.command_status! cmd - |> Result.mapErr InternalIOErr.handle_err - |> Result.mapErr CmdStatusErr +status! = \@Cmd(cmd) -> + Host.command_status!(cmd) + |> Result.map_err(InternalIOErr.handle_err) + |> Result.map_err(CmdStatusErr) ## Execute command and inherit stdin, stdout and stderr from parent ## @@ -124,11 +126,11 @@ status! = \@Cmd cmd -> exec! : Str, List Str => Result {} [CmdStatusErr InternalIOErr.IOErr] exec! = \program, arguments -> exit_code = - new program - |> args arguments + new(program) + |> args(arguments) |> status!? if exit_code == 0i32 then - Ok {} + Ok({}) else - Err (CmdStatusErr (Other "Non-zero exit code $(Num.toStr exit_code)")) + Err(CmdStatusErr(Other("Non-zero exit code $(Num.to_str(exit_code))"))) diff --git a/platform/Dir.roc b/platform/Dir.roc index 5fd9a9be..14e6411e 100644 --- a/platform/Dir.roc +++ b/platform/Dir.roc @@ -26,7 +26,7 @@ DirEntry : Path.DirEntry ## > [Path.list_dir!] does the same thing, except it takes a [Path] instead of a [Str]. list! : Str => Result (List Path) [DirErr IOErr] list! = \path -> - Path.list_dir! (Path.from_str path) + Path.list_dir!(Path.from_str(path)) ## Deletes a directory if it's empty ## @@ -39,7 +39,7 @@ list! = \path -> ## > [Path.delete_empty!] does the same thing, except it takes a [Path] instead of a [Str]. delete_empty! : Str => Result {} [DirErr IOErr] delete_empty! = \path -> - Path.delete_empty! (Path.from_str path) + Path.delete_empty!(Path.from_str(path)) ## Recursively deletes the directory as well as all files and directories ## inside it. @@ -53,7 +53,7 @@ delete_empty! = \path -> ## > [Path.delete_all!] does the same thing, except it takes a [Path] instead of a [Str]. delete_all! : Str => Result {} [DirErr IOErr] delete_all! = \path -> - Path.delete_all! (Path.from_str path) + Path.delete_all!(Path.from_str(path)) ## Creates a directory ## @@ -65,7 +65,7 @@ delete_all! = \path -> ## > [Path.create_dir!] does the same thing, except it takes a [Path] instead of a [Str]. create! : Str => Result {} [DirErr IOErr] create! = \path -> - Path.create_dir! (Path.from_str path) + Path.create_dir!(Path.from_str(path)) ## Creates a directory recursively adding any missing parent directories. ## @@ -76,4 +76,4 @@ create! = \path -> ## > [Path.create_all!] does the same thing, except it takes a [Path] instead of a [Str]. create_all! : Str => Result {} [DirErr IOErr] create_all! = \path -> - Path.create_all! (Path.from_str path) + Path.create_all!(Path.from_str(path)) diff --git a/platform/Env.roc b/platform/Env.roc index 14c015f2..00aeaa07 100644 --- a/platform/Env.roc +++ b/platform/Env.roc @@ -18,27 +18,27 @@ import Host ## from the environment. File operations on relative [Path]s are relative to this directory. cwd! : {} => Result Path [CwdUnavailable] cwd! = \{} -> - bytes = Host.cwd! {} |> Result.withDefault [] + bytes = Host.cwd!({}) |> Result.with_default([]) - if List.isEmpty bytes then - Err CwdUnavailable + if List.is_empty(bytes) then + Err(CwdUnavailable) else - Ok (InternalPath.from_arbitrary_bytes bytes) + Ok(InternalPath.from_arbitrary_bytes(bytes)) ## Sets the [current working directory](https://en.wikipedia.org/wiki/Working_directory) ## in the environment. After changing it, file operations on relative [Path]s will be relative ## to this directory. set_cwd! : Path => Result {} [InvalidCwd] set_cwd! = \path -> - Host.set_cwd! (InternalPath.to_bytes path) - |> Result.mapErr \{} -> InvalidCwd + Host.set_cwd!(InternalPath.to_bytes(path)) + |> Result.map_err(\{} -> InvalidCwd) ## Gets the path to the currently-running executable. exe_path! : {} => Result Path [ExePathUnavailable] exe_path! = \{} -> - when Host.exe_path! {} is - Ok bytes -> Ok (InternalPath.from_os_bytes bytes) - Err {} -> Err ExePathUnavailable + when Host.exe_path!({}) is + Ok(bytes) -> Ok(InternalPath.from_os_bytes(bytes)) + Err({}) -> Err(ExePathUnavailable) ## Reads the given environment variable. ## @@ -46,8 +46,8 @@ exe_path! = \{} -> ## [Unicode replacement character](https://unicode.org/glossary/#replacement_character) ('οΏ½'). var! : Str => Result Str [VarNotFound] var! = \name -> - Host.env_var! name - |> Result.mapErr \{} -> VarNotFound + Host.env_var!(name) + |> Result.map_err(\{} -> VarNotFound) ## Reads the given environment variable and attempts to decode it. ## @@ -76,12 +76,12 @@ var! = \name -> ## decode! : Str => Result val [VarNotFound, DecodeErr DecodeError] where val implements Decoding decode! = \name -> - when Host.env_var! name is - Err {} -> Err VarNotFound - Ok var_str -> - Str.toUtf8 var_str - |> Decode.fromBytes (EnvDecoding.format {}) - |> Result.mapErr (\_ -> DecodeErr TooShort) + when Host.env_var!(name) is + Err({}) -> Err(VarNotFound) + Ok(var_str) -> + Str.to_utf8(var_str) + |> Decode.from_bytes(EnvDecoding.format({})) + |> Result.map_err(\_ -> DecodeErr(TooShort)) ## Reads all the process's environment variables into a [Dict]. ## @@ -89,8 +89,8 @@ decode! = \name -> ## will be used in place of any parts of keys or values that are invalid Unicode. dict! : {} => Dict Str Str dict! = \{} -> - Host.env_dict! {} - |> Dict.fromList + Host.env_dict!({}) + |> Dict.from_list # ## Walks over the process's environment variables as key-value arguments to the walking function. # ## @@ -137,7 +137,7 @@ OS : [LINUX, MACOS, WINDOWS, OTHER Str] platform! : {} => { arch : ARCH, os : OS } platform! = \{} -> - from_rust = Host.current_arch_os! {} + from_rust = Host.current_arch_os!({}) arch = when from_rust.arch is @@ -145,14 +145,14 @@ platform! = \{} -> "x86_64" -> X64 "arm" -> ARM "aarch64" -> AARCH64 - _ -> OTHER from_rust.arch + _ -> OTHER(from_rust.arch) os = when from_rust.os is "linux" -> LINUX "macos" -> MACOS "windows" -> WINDOWS - _ -> OTHER from_rust.os + _ -> OTHER(from_rust.os) { arch, os } @@ -167,5 +167,5 @@ platform! = \{} -> ## temp_dir! : {} => Path temp_dir! = \{} -> - Host.temp_dir! {} + Host.temp_dir!({}) |> InternalPath.from_os_bytes diff --git a/platform/EnvDecoding.roc b/platform/EnvDecoding.roc index 0ca77b55..2b819e9a 100644 --- a/platform/EnvDecoding.roc +++ b/platform/EnvDecoding.roc @@ -27,82 +27,95 @@ EnvFormat := {} implements [ ] format : {} -> EnvFormat -format = \{} -> @EnvFormat {} +format = \{} -> @EnvFormat({}) decode_bytes_to_num = \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 } + when Str.from_utf8(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 } + Err(_) -> { result: Err(TooShort), rest: bytes } -env_u8 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU8 -env_u16 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU16 -env_u32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU32 -env_u64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU64 -env_u128 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toU128 -env_i8 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI8 -env_i16 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI16 -env_i32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI32 -env_i64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI64 -env_i128 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toI128 -env_f32 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toF32 -env_f64 = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toF64 -env_dec = Decode.custom \bytes, @EnvFormat {} -> decode_bytes_to_num bytes Str.toDec +env_u8 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u8)) +env_u16 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u16)) +env_u32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u32)) +env_u64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u64)) +env_u128 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_u128)) +env_i8 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i8)) +env_i16 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i16)) +env_i32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i32)) +env_i64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i64)) +env_i128 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_i128)) +env_f32 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_f32)) +env_f64 = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_f64)) +env_dec = Decode.custom(\bytes, @EnvFormat({}) -> decode_bytes_to_num(bytes, Str.to_dec)) -env_bool = 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 } +env_bool = Decode.custom( + \bytes, @EnvFormat({}) -> + when Str.from_utf8(bytes) is + Ok("true") -> { result: Ok(Bool.true), rest: [] } + Ok("false") -> { result: Ok(Bool.false), rest: [] } + _ -> { result: Err(TooShort), rest: bytes }, +) -env_string = Decode.custom \bytes, @EnvFormat {} -> - when Str.fromUtf8 bytes is - Ok s -> { result: Ok s, rest: [] } - Err _ -> { result: Err TooShort, rest: bytes } +env_string = Decode.custom( + \bytes, @EnvFormat({}) -> + when Str.from_utf8(bytes) is + Ok(s) -> { result: Ok(s), rest: [] } + Err(_) -> { result: Err(TooShort), rest: bytes }, +) -env_list = \decode_elem -> 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. - decode_elems = \all_bytes, accum -> - { to_parse, remainder } = - when List.splitFirst all_bytes (Num.toU8 ',') is - Ok { before, after } -> - { to_parse: before, remainder: Some after } +env_list = \decode_elem -> + 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. + decode_elems = \all_bytes, accum -> + { to_parse, remainder } = + when List.split_first(all_bytes, Num.to_u8(',')) is + Ok({ before, after }) -> + { to_parse: before, remainder: Some(after) } - Err NotFound -> - { to_parse: all_bytes, remainder: None } + Err(NotFound) -> + { to_parse: all_bytes, remainder: None } - when Decode.decodeWith to_parse decode_elem (@EnvFormat {}) is - { result, rest } -> - when result is - Ok val -> - when remainder is - Some rest_bytes -> decode_elems rest_bytes (List.append accum val) - None -> Done (List.append accum val) + when Decode.decode_with(to_parse, decode_elem, @EnvFormat({})) is + { result, rest } -> + when result is + Ok(val) -> + when remainder is + Some(rest_bytes) -> decode_elems(rest_bytes, List.append(accum, val)) + None -> Done(List.append(accum, val)) - Err e -> Errored e rest + Err(e) -> Errored(e, rest) - when decode_elems bytes [] is - Errored e rest -> { result: Err e, rest } - Done vals -> - { result: Ok vals, rest: [] } + when decode_elems(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. env_record : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_, _ -> _) -> Decoder _ _ -env_record = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> - { result: Err TooShort, rest: bytes } +env_record = \_initialState, _stepField, _finalizer -> + Decode.custom( + \bytes, @EnvFormat({}) -> + { result: Err(TooShort), rest: bytes }, + ) # 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. env_tuple : _, (_, _ -> [Next (Decoder _ _), TooLong]), (_ -> _) -> Decoder _ _ -env_tuple = \_initialState, _stepElem, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> - { result: Err TooShort, rest: bytes } +env_tuple = \_initialState, _stepElem, _finalizer -> + Decode.custom( + \bytes, @EnvFormat({}) -> + { result: Err(TooShort), rest: bytes }, + ) diff --git a/platform/File.roc b/platform/File.roc index 5d972c8f..dc762938 100644 --- a/platform/File.roc +++ b/platform/File.roc @@ -65,7 +65,7 @@ IOErr : InternalIOErr.IOErr ## > [Path.write!] does the same thing, except it takes a [Path] instead of a [Str]. write! : val, Str, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting write! = \val, path, fmt -> - Path.write! val (Path.from_str path) fmt + Path.write!(val, Path.from_str(path), fmt) ## Writes bytes to a file. ## @@ -81,7 +81,7 @@ write! = \val, path, fmt -> ## > [Path.write_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. write_bytes! : List U8, Str => Result {} [FileWriteErr Path IOErr] write_bytes! = \bytes, path -> - Path.write_bytes! bytes (Path.from_str path) + Path.write_bytes!(bytes, Path.from_str(path)) ## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## @@ -97,7 +97,7 @@ write_bytes! = \bytes, path -> ## > [Path.write_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. write_utf8! : Str, Str => Result {} [FileWriteErr Path IOErr] write_utf8! = \str, path -> - Path.write_utf8! str (Path.from_str path) + Path.write_utf8!(str, Path.from_str(path)) ## Deletes a file from the filesystem. ## @@ -121,7 +121,7 @@ write_utf8! = \str, path -> ## > [Path.delete!] does the same thing, except it takes a [Path] instead of a [Str]. delete! : Str => Result {} [FileWriteErr Path IOErr] delete! = \path -> - Path.delete! (Path.from_str path) + Path.delete!(Path.from_str(path)) ## Reads all the bytes in a file. ## @@ -137,7 +137,7 @@ delete! = \path -> ## > [Path.read_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. read_bytes! : Str => Result (List U8) [FileReadErr Path IOErr] read_bytes! = \path -> - Path.read_bytes! (Path.from_str path) + Path.read_bytes!(Path.from_str(path)) ## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## @@ -154,7 +154,7 @@ read_bytes! = \path -> ## > [Path.read_utf8!] does the same thing, except it takes a [Path] instead of a [Str]. read_utf8! : Str => Result Str [FileReadErr Path IOErr, FileReadUtf8Err Path _] read_utf8! = \path -> - Path.read_utf8! (Path.from_str path) + Path.read_utf8!(Path.from_str(path)) # read : Str, fmt => Result contents [FileReadErr Path ReadErr, FileReadDecodingFailed] where contents implements Decoding, fmt implements DecoderFormatting # read = \path, fmt -> @@ -170,7 +170,7 @@ read_utf8! = \path -> ## > [Path.hard_link!] does the same thing, except it takes a [Path] instead of a [Str]. hard_link! : Str => Result {} [LinkErr IOErr] hard_link! = \path -> - Path.hard_link! (Path.from_str path) + Path.hard_link!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a directory. ## Returns False if the path exists and it is not a directory. If the path does not exist, @@ -181,7 +181,7 @@ hard_link! = \path -> ## > [Path.is_dir!] does the same thing, except it takes a [Path] instead of a [Str]. is_dir! : Str => Result Bool [PathErr IOErr] is_dir! = \path -> - Path.is_dir! (Path.from_str path) + Path.is_dir!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a regular file. ## Returns False if the path exists and it is not a file. If the path does not exist, @@ -192,7 +192,7 @@ is_dir! = \path -> ## > [Path.is_file!] does the same thing, except it takes a [Path] instead of a [Str]. is_file! : Str => Result Bool [PathErr IOErr] is_file! = \path -> - Path.is_file! (Path.from_str path) + Path.is_file!(Path.from_str(path)) ## Returns True if the path exists on disk and is pointing at a symbolic link. ## Returns False if the path exists and it is not a symbolic link. If the path does not exist, @@ -203,7 +203,7 @@ is_file! = \path -> ## > [Path.is_sym_link!] does the same thing, except it takes a [Path] instead of a [Str]. is_sym_link! : Str => Result Bool [PathErr IOErr] is_sym_link! = \path -> - Path.is_sym_link! (Path.from_str path) + Path.is_sym_link!(Path.from_str(path)) ## Return the type of the path if the path exists on disk. ## This uses [rust's std::path::is_symlink](https://doc.rust-lang.org/std/path/struct.Path.html#method.is_symlink). @@ -211,7 +211,7 @@ is_sym_link! = \path -> ## > [Path.type!] does the same thing, except it takes a [Path] instead of a [Str]. type! : Str => Result [IsFile, IsDir, IsSymLink] [PathErr IOErr] type! = \path -> - Path.type! (Path.from_str path) + Path.type!(Path.from_str(path)) Reader := { reader : Host.FileReader, path : Path } @@ -223,12 +223,12 @@ Reader := { reader : Host.FileReader, path : Path } ## Use [read_utf8!] if you want to get the entire file contents at once. open_reader! : Str => Result Reader [GetFileReadErr Path IOErr] open_reader! = \path_str -> - path = Path.from_str path_str + path = Path.from_str(path_str) # 0 means with default capacity - Host.file_reader! (Str.toUtf8 path_str) 0 - |> Result.mapErr \err -> GetFileReadErr path (InternalIOErr.handle_err err) - |> Result.map \reader -> @Reader { reader, path } + Host.file_reader!(Str.to_utf8(path_str), 0) + |> Result.map_err(\err -> GetFileReadErr(path, InternalIOErr.handle_err(err))) + |> Result.map(\reader -> @Reader({ reader, path })) ## Try to open a `File.Reader` for buffered (= part by part) reading given a path string. ## The buffer will be created with the specified capacity. @@ -239,11 +239,11 @@ open_reader! = \path_str -> ## Use [read_utf8!] if you want to get the entire file contents at once. open_reader_with_capacity! : Str, U64 => Result Reader [GetFileReadErr Path IOErr] open_reader_with_capacity! = \path_str, capacity -> - path = Path.from_str path_str + path = Path.from_str(path_str) - Host.file_reader! (Str.toUtf8 path_str) capacity - |> Result.mapErr \err -> GetFileReadErr path (InternalIOErr.handle_err err) - |> Result.map \reader -> @Reader { reader, path } + Host.file_reader!(Str.to_utf8(path_str), capacity) + |> Result.map_err(\err -> GetFileReadErr(path, InternalIOErr.handle_err(err))) + |> Result.map(\reader -> @Reader({ reader, path })) ## Try to read a line from a file given a Reader. ## The line will be provided as the list of bytes (`List U8`) until a newline (`0xA` byte). @@ -254,6 +254,6 @@ open_reader_with_capacity! = \path_str, capacity -> ## ## Use [read_utf8!] if you want to get the entire file contents at once. read_line! : Reader => Result (List U8) [FileReadErr Path IOErr] -read_line! = \@Reader { reader, path } -> - Host.file_read_line! reader - |> Result.mapErr \err -> FileReadErr path (InternalIOErr.handle_err err) +read_line! = \@Reader({ reader, path }) -> + Host.file_read_line!(reader) + |> Result.map_err(\err -> FileReadErr(path, InternalIOErr.handle_err(err))) diff --git a/platform/FileMetadata.roc b/platform/FileMetadata.roc index b66dbc17..a78b4a96 100644 --- a/platform/FileMetadata.roc +++ b/platform/FileMetadata.roc @@ -20,19 +20,19 @@ FileMetadata := { ## Returns the number of bytes in the associated file. bytes : FileMetadata -> U64 -bytes = \@FileMetadata info -> info.bytes +bytes = \@FileMetadata(info) -> info.bytes ## Returns [Bool.true] if the associated file is read-only. is_readonly : FileMetadata -> Bool -is_readonly = \@FileMetadata info -> info.is_readonly +is_readonly = \@FileMetadata(info) -> info.is_readonly ## Returns the type of the associated file. type : FileMetadata -> [File, Dir, Symlink] -type = \@FileMetadata info -> info.type +type = \@FileMetadata(info) -> info.type ## Returns the mode of the associated file. mode : FileMetadata -> [Unix U32, NonUnix] -mode = \@FileMetadata info -> info.mode +mode = \@FileMetadata(info) -> info.mode # TODO need to create a Time module and return something like Time.Utc here. # lastModified : FileMetadata -> Utc diff --git a/platform/Http.roc b/platform/Http.roc index 696b7fff..0fec9c22 100644 --- a/platform/Http.roc +++ b/platform/Http.roc @@ -82,15 +82,15 @@ send! = \request -> ## ``` get! : Str, fmt => Result body [HttpDecodingFailed] where body implements Decoding, fmt implements DecoderFormatting get! = \uri, fmt -> - response = send! { default_request & uri } + response = send!({ default_request & uri }) - Decode.fromBytes response.body fmt - |> Result.mapErr \_ -> HttpDecodingFailed + Decode.from_bytes(response.body, fmt) + |> Result.map_err(\_ -> HttpDecodingFailed) get_utf8! : Str => Result Str [BadBody Str] get_utf8! = \uri -> - response = send! { default_request & uri } + response = send!({ default_request & uri }) response.body - |> Str.fromUtf8 - |> Result.mapErr \_ -> BadBody "Invalid UTF-8" + |> Str.from_utf8 + |> Result.map_err(\_ -> BadBody("Invalid UTF-8")) diff --git a/platform/InternalCmd.roc b/platform/InternalCmd.roc index 2fe8f9bb..7be8a527 100644 --- a/platform/InternalCmd.roc +++ b/platform/InternalCmd.roc @@ -22,7 +22,7 @@ Output : { from_host_output : OutputFromHost -> Output from_host_output = \{ status, stdout, stderr } -> { - status: Result.mapErr status InternalIOErr.handle_err, + status: Result.map_err(status, InternalIOErr.handle_err), stdout, stderr, } diff --git a/platform/InternalDateTime.roc b/platform/InternalDateTime.roc index 0b219e73..cf9dd220 100644 --- a/platform/InternalDateTime.roc +++ b/platform/InternalDateTime.roc @@ -8,18 +8,18 @@ DateTime : { year : I128, month : I128, day : I128, hours : I128, minutes : I128 to_iso_8601 : DateTime -> Str to_iso_8601 = \{ year, month, day, hours, minutes, seconds } -> - year_str = year_with_padded_zeros year - month_str = month_with_padded_zeros month - day_str = day_with_padded_zeros day - hour_str = hours_with_padded_zeros hours - minute_str = minutes_with_padded_zeros minutes - seconds_str = seconds_with_padded_zeros seconds + year_str = year_with_padded_zeros(year) + month_str = month_with_padded_zeros(month) + day_str = day_with_padded_zeros(day) + hour_str = hours_with_padded_zeros(hours) + minute_str = minutes_with_padded_zeros(minutes) + seconds_str = seconds_with_padded_zeros(seconds) "$(year_str)-$(month_str)-$(day_str)T$(hour_str):$(minute_str):$(seconds_str)Z" year_with_padded_zeros : I128 -> Str year_with_padded_zeros = \year -> - year_str = Num.toStr year + year_str = Num.to_str(year) if year < 10 then "000$(year_str)" else if year < 100 then @@ -31,7 +31,7 @@ year_with_padded_zeros = \year -> month_with_padded_zeros : I128 -> Str month_with_padded_zeros = \month -> - month_str = Num.toStr month + month_str = Num.to_str(month) if month < 10 then "0$(month_str)" else @@ -59,37 +59,37 @@ is_leap_year = \year -> (year % 400 == 0) # expecpt when also divisible by 400 ) -expect is_leap_year 2000 -expect is_leap_year 2012 -expect !(is_leap_year 1900) -expect !(is_leap_year 2015) -expect List.map [2023, 1988, 1992, 1996] is_leap_year == [Bool.false, Bool.true, Bool.true, Bool.true] -expect List.map [1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600] is_leap_year == [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false] +expect is_leap_year(2000) +expect is_leap_year(2012) +expect !(is_leap_year(1900)) +expect !(is_leap_year(2015)) +expect List.map([2023, 1988, 1992, 1996], is_leap_year) == [Bool.false, Bool.true, Bool.true, Bool.true] +expect List.map([1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600], is_leap_year) == [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false] days_in_month : I128, I128 -> I128 days_in_month = \year, month -> - if List.contains [1, 3, 5, 7, 8, 10, 12] month then + if List.contains([1, 3, 5, 7, 8, 10, 12], month) then 31 - else if List.contains [4, 6, 9, 11] month then + else if List.contains([4, 6, 9, 11], month) then 30 else if month == 2 then - (if is_leap_year year then 29 else 28) + (if is_leap_year(year) then 29 else 28) else 0 -expect days_in_month 2023 1 == 31 # January -expect days_in_month 2023 2 == 28 # February -expect days_in_month 1996 2 == 29 # February in a leap year -expect days_in_month 2023 3 == 31 # March -expect days_in_month 2023 4 == 30 # April -expect days_in_month 2023 5 == 31 # May -expect days_in_month 2023 6 == 30 # June -expect days_in_month 2023 7 == 31 # July -expect days_in_month 2023 8 == 31 # August -expect days_in_month 2023 9 == 30 # September -expect days_in_month 2023 10 == 31 # October -expect days_in_month 2023 11 == 30 # November -expect days_in_month 2023 12 == 31 # December +expect days_in_month(2023, 1) == 31 # January +expect days_in_month(2023, 2) == 28 # February +expect days_in_month(1996, 2) == 29 # February in a leap year +expect days_in_month(2023, 3) == 31 # March +expect days_in_month(2023, 4) == 30 # April +expect days_in_month(2023, 5) == 31 # May +expect days_in_month(2023, 6) == 30 # June +expect days_in_month(2023, 7) == 31 # July +expect days_in_month(2023, 8) == 31 # August +expect days_in_month(2023, 9) == 30 # September +expect days_in_month(2023, 10) == 31 # October +expect days_in_month(2023, 11) == 30 # November +expect days_in_month(2023, 12) == 31 # December epoch_millis_to_datetime : I128 -> DateTime epoch_millis_to_datetime = \millis -> @@ -100,56 +100,63 @@ epoch_millis_to_datetime = \millis -> month = 1 year = 1970 - epoch_millis_to_datetimeHelp { - year, - month, - day, - hours: hours % 24, - minutes: minutes % 60, - seconds: seconds % 60, - } + epoch_millis_to_datetimeHelp( + { + year, + month, + day, + hours: hours % 24, + minutes: minutes % 60, + seconds: seconds % 60, + }, + ) epoch_millis_to_datetimeHelp : DateTime -> DateTime epoch_millis_to_datetimeHelp = \current -> - count_days_in_month = days_in_month current.year current.month + count_days_in_month = days_in_month(current.year, current.month) count_days_in_prev_month = if current.month == 1 then - days_in_month (current.year - 1) 12 + days_in_month((current.year - 1), 12) else - days_in_month current.year (current.month - 1) + days_in_month(current.year, (current.month - 1)) if current.day < 1 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & year: if current.month == 1 then current.year - 1 else current.year, month: if current.month == 1 then 12 else current.month - 1, day: current.day + count_days_in_prev_month, - } + }, + ) else if current.hours < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & day: current.day - 1, hours: current.hours + 24, - } + }, + ) else if current.minutes < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & hours: current.hours - 1, minutes: current.minutes + 60, - } + }, + ) else if current.seconds < 0 then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & minutes: current.minutes - 1, seconds: current.seconds + 60, - } + }, + ) else if current.day > count_days_in_month then - epoch_millis_to_datetimeHelp + epoch_millis_to_datetimeHelp( { current & year: if current.month == 12 then current.year + 1 else current.year, month: if current.month == 12 then 1 else current.month + 1, day: current.day - count_days_in_month, - } + }, + ) else current diff --git a/platform/InternalHttp.roc b/platform/InternalHttp.roc index 3936ee77..e994ca09 100644 --- a/platform/InternalHttp.roc +++ b/platform/InternalHttp.roc @@ -64,12 +64,12 @@ to_host_response = \{ status, headers, body } -> { to_host_request : Request -> RequestToAndFromHost to_host_request = \{ method, headers, uri, body, timeout_ms } -> { - method: to_host_method method, - method_ext: to_host_method_ext method, + method: to_host_method(method), + method_ext: to_host_method_ext(method), headers, uri, body, - timeout_ms: to_host_timeout timeout_ms, + timeout_ms: to_host_timeout(timeout_ms), } to_host_method : Method -> _ @@ -84,27 +84,27 @@ to_host_method = \method -> TRACE -> 9 CONNECT -> 0 PATCH -> 6 - EXTENSION _ -> 2 + EXTENSION(_) -> 2 to_host_method_ext : Method -> Str to_host_method_ext = \method -> when method is - EXTENSION ext -> ext + EXTENSION(ext) -> ext _ -> "" to_host_timeout : _ -> U64 to_host_timeout = \timeout -> when timeout is - TimeoutMilliseconds ms -> ms + TimeoutMilliseconds(ms) -> ms NoTimeout -> 0 from_host_request : RequestToAndFromHost -> Request from_host_request = \{ method, method_ext, headers, uri, body, timeout_ms } -> { - method: from_host_method method method_ext, + method: from_host_method(method, method_ext), headers, uri, body, - timeout_ms: from_host_timeout timeout_ms, + timeout_ms: from_host_timeout(timeout_ms), } from_host_method : U64, Str -> Method @@ -119,17 +119,17 @@ from_host_method = \tag, ext -> 9 -> TRACE 0 -> CONNECT 6 -> PATCH - 2 -> EXTENSION ext - _ -> crash "invalid tag from host" + 2 -> EXTENSION(ext) + _ -> crash("invalid tag from host") from_host_timeout : U64 -> [TimeoutMilliseconds U64, NoTimeout] from_host_timeout = \timeout -> when timeout is 0 -> NoTimeout - _ -> TimeoutMilliseconds timeout + _ -> TimeoutMilliseconds(timeout) -expect from_host_timeout 0 == NoTimeout -expect from_host_timeout 1 == TimeoutMilliseconds 1 +expect from_host_timeout(0) == NoTimeout +expect from_host_timeout(1) == TimeoutMilliseconds(1) from_host_response : ResponseToAndFromHost -> Response from_host_response = \{ status, headers, body } -> { diff --git a/platform/InternalIOErr.roc b/platform/InternalIOErr.roc index ef95d767..5d5f20e5 100644 --- a/platform/InternalIOErr.roc +++ b/platform/InternalIOErr.roc @@ -55,4 +55,4 @@ handle_err = \{ tag, msg } -> Interrupted -> Interrupted Unsupported -> Unsupported OutOfMemory -> OutOfMemory - Other | EndOfFile -> Other msg + Other | EndOfFile -> Other(msg) diff --git a/platform/InternalPath.roc b/platform/InternalPath.roc index 258fc8b1..05779bf7 100644 --- a/platform/InternalPath.roc +++ b/platform/InternalPath.roc @@ -57,22 +57,22 @@ wrap : UnwrappedPath -> InternalPath wrap = @InternalPath unwrap : InternalPath -> UnwrappedPath -unwrap = \@InternalPath raw -> raw +unwrap = \@InternalPath(raw) -> raw ## TODO do this in the host, and iterate over the Str ## bytes when possible instead of always converting to ## a heap-allocated List. to_bytes : InternalPath -> List U8 -to_bytes = \@InternalPath path -> +to_bytes = \@InternalPath(path) -> when path is - FromOperatingSystem bytes -> bytes - ArbitraryBytes bytes -> bytes - FromStr str -> Str.toUtf8 str + FromOperatingSystem(bytes) -> bytes + ArbitraryBytes(bytes) -> bytes + FromStr(str) -> Str.to_utf8(str) from_arbitrary_bytes : List U8 -> InternalPath from_arbitrary_bytes = \bytes -> - @InternalPath (ArbitraryBytes bytes) + @InternalPath(ArbitraryBytes(bytes)) from_os_bytes : List U8 -> InternalPath from_os_bytes = \bytes -> - @InternalPath (FromOperatingSystem bytes) + @InternalPath(FromOperatingSystem(bytes)) diff --git a/platform/MultipartFormData.roc b/platform/MultipartFormData.roc index 585a2262..72960e2f 100644 --- a/platform/MultipartFormData.roc +++ b/platform/MultipartFormData.roc @@ -54,140 +54,167 @@ doubledash = ['-', '-'] ## ``` ## parse_content_f : { upper : List U8, lower : List U8 } -> (List U8 -> Result { value : List U8, rest : List U8 } _) -parse_content_f = \{ upper, lower } -> \bytes -> - - toSearchUpper = List.concat newline upper - toSearchLower = List.concat newline lower - searchLength = List.len toSearchUpper - afterSearch = List.sublist bytes { start: searchLength, len: Num.maxU64 } - - if - List.startsWith bytes toSearchUpper - || List.startsWith bytes toSearchLower - then - nextLineStart = afterSearch |> List.findFirstIndex? \b -> b == '\r' - - Ok { - value: List.sublist afterSearch { start: 0, len: nextLineStart }, - rest: List.sublist afterSearch { start: nextLineStart, len: Num.maxU64 }, - } - else - Err ExpectedContent - -parseContentDispositionF = parse_content_f { - upper: Str.toUtf8 "Content-Disposition:", - lower: Str.toUtf8 "content-disposition:", -} +parse_content_f = \{ upper, lower } -> + \bytes -> + + to_search_upper = List.concat(newline, upper) + to_search_lower = List.concat(newline, lower) + search_length = List.len(to_search_upper) + after_search = List.sublist(bytes, { start: search_length, len: Num.max_u64 }) + + if + List.starts_with(bytes, to_search_upper) + || List.starts_with(bytes, to_search_lower) + then + next_line_start = after_search |> List.find_first_index?(\b -> b == '\r') + + Ok( + { + value: List.sublist(after_search, { start: 0, len: next_line_start }), + rest: List.sublist(after_search, { start: next_line_start, len: Num.max_u64 }), + }, + ) + else + Err(ExpectedContent) + +parse_content_disposition_f = parse_content_f( + { + upper: Str.to_utf8("Content-Disposition:"), + lower: Str.to_utf8("content-disposition:"), + }, +) expect - input = Str.toUtf8 "\r\nContent-Disposition: form-data; name=\"sometext\"\r\nSome text here..." - actual = parseContentDispositionF input - expected = Ok { - value: Str.toUtf8 " form-data; name=\"sometext\"", - rest: Str.toUtf8 "\r\nSome text here...", - } + input = Str.to_utf8("\r\nContent-Disposition: form-data; name=\"sometext\"\r\nSome text here...") + actual = parse_content_disposition_f(input) + expected = Ok( + { + value: Str.to_utf8(" form-data; name=\"sometext\""), + rest: Str.to_utf8("\r\nSome text here..."), + }, + ) actual == expected -parseContentTypeF = parse_content_f { - upper: Str.toUtf8 "Content-Type:", - lower: Str.toUtf8 "content-type:", -} +parse_content_type_f = parse_content_f( + { + upper: Str.to_utf8("Content-Type:"), + lower: Str.to_utf8("content-type:"), + }, +) expect - input = Str.toUtf8 "\r\ncontent-type: multipart/mixed; boundary=abcde\r\nSome text here..." - actual = parseContentTypeF input - expected = Ok { - value: Str.toUtf8 " multipart/mixed; boundary=abcde", - rest: Str.toUtf8 "\r\nSome text here...", - } + input = Str.to_utf8("\r\ncontent-type: multipart/mixed; boundary=abcde\r\nSome text here...") + actual = parse_content_type_f(input) + expected = Ok( + { + value: Str.to_utf8(" multipart/mixed; boundary=abcde"), + rest: Str.to_utf8("\r\nSome text here..."), + }, + ) actual == expected -parseContentTransferEncodingF = parse_content_f { - upper: Str.toUtf8 "Content-Transfer-Encoding:", - lower: Str.toUtf8 "content-transfer-encoding:", -} +parse_content_transfer_encoding_f = parse_content_f( + { + upper: Str.to_utf8("Content-Transfer-Encoding:"), + lower: Str.to_utf8("content-transfer-encoding:"), + }, +) expect - input = Str.toUtf8 "\r\nContent-Transfer-Encoding: binary\r\nSome text here..." - actual = parseContentTransferEncodingF input - expected = Ok { - value: Str.toUtf8 " binary", - rest: Str.toUtf8 "\r\nSome text here...", - } + input = Str.to_utf8("\r\nContent-Transfer-Encoding: binary\r\nSome text here...") + actual = parse_content_transfer_encoding_f(input) + expected = Ok( + { + value: Str.to_utf8(" binary"), + rest: Str.to_utf8("\r\nSome text here..."), + }, + ) actual == expected ## Parses all headers: Content-Disposition, Content-Type and Content-Transfer-Encoding. -parseAllHeaders : List U8 -> Result FormData _ -parseAllHeaders = \bytes -> - - doubleNewlineLength = 4 # \r\n\r\n - - when parseContentDispositionF bytes is - Err err -> Err (ExpectedContentDisposition bytes err) - Ok { value: disposition, rest: first } -> - when parseContentTypeF first is - Err _ -> - Ok { - disposition, - type: [], - encoding: [], - data: List.dropFirst first doubleNewlineLength, - } - - Ok { value: type, rest: second } -> - when parseContentTransferEncodingF second is - Err _ -> - Ok { - disposition, - type, - encoding: [], - data: List.dropFirst second doubleNewlineLength, - } - - Ok { value: encoding, rest } -> - Ok { - disposition, - type, - encoding, - data: List.dropFirst rest doubleNewlineLength, - } +parse_all_headers : List U8 -> Result FormData _ +parse_all_headers = \bytes -> + + double_newline_length = 4 # \r\n\r\n + + when parse_content_disposition_f(bytes) is + Err(err) -> Err(ExpectedContentDisposition(bytes, err)) + Ok({ value: disposition, rest: first }) -> + when parse_content_type_f(first) is + Err(_) -> + Ok( + { + disposition, + type: [], + encoding: [], + data: List.drop_first(first, double_newline_length), + }, + ) + + Ok({ value: type, rest: second }) -> + when parse_content_transfer_encoding_f(second) is + Err(_) -> + Ok( + { + disposition, + type, + encoding: [], + data: List.drop_first(second, double_newline_length), + }, + ) + + Ok({ value: encoding, rest }) -> + Ok( + { + disposition, + type, + encoding, + data: List.drop_first(rest, double_newline_length), + }, + ) expect header = "\r\nContent-Disposition: form-data; name=\"sometext\"\r\n\r\n" - actual = parseAllHeaders (Str.toUtf8 header) - expected = Ok { - disposition: Str.toUtf8 " form-data; name=\"sometext\"", - type: Str.toUtf8 "", - encoding: Str.toUtf8 "", - data: Str.toUtf8 "", - } + actual = parse_all_headers(Str.to_utf8(header)) + expected = Ok( + { + disposition: Str.to_utf8(" form-data; name=\"sometext\""), + type: Str.to_utf8(""), + encoding: Str.to_utf8(""), + data: Str.to_utf8(""), + }, + ) actual == expected expect header = "\r\nContent-Disposition: form-data; name=\"sometext\"\r\nContent-Type: multipart/mixed; boundary=abcde\r\n\r\n" - actual = parseAllHeaders (Str.toUtf8 header) - expected = Ok { - disposition: Str.toUtf8 " form-data; name=\"sometext\"", - type: Str.toUtf8 " multipart/mixed; boundary=abcde", - encoding: Str.toUtf8 "", - data: Str.toUtf8 "", - } + actual = parse_all_headers(Str.to_utf8(header)) + expected = Ok( + { + disposition: Str.to_utf8(" form-data; name=\"sometext\""), + type: Str.to_utf8(" multipart/mixed; boundary=abcde"), + encoding: Str.to_utf8(""), + data: Str.to_utf8(""), + }, + ) actual == expected expect header = "\r\nContent-Disposition: form-data; name=\"sometext\"\r\nContent-Type: multipart/mixed; boundary=abcde\r\nContent-Transfer-Encoding: binary\r\n\r\n" - actual = parseAllHeaders (Str.toUtf8 header) - expected = Ok { - disposition: Str.toUtf8 " form-data; name=\"sometext\"", - type: Str.toUtf8 " multipart/mixed; boundary=abcde", - encoding: Str.toUtf8 " binary", - data: Str.toUtf8 "", - } + actual = parse_all_headers(Str.to_utf8(header)) + expected = Ok( + { + disposition: Str.to_utf8(" form-data; name=\"sometext\""), + type: Str.to_utf8(" multipart/mixed; boundary=abcde"), + encoding: Str.to_utf8(" binary"), + data: Str.to_utf8(""), + }, + ) actual == expected @@ -210,23 +237,23 @@ parse_form_data : -> Result (List FormData) [ExpectedEnclosedByBoundary] parse_form_data = \{ body, boundary } -> - startMarker = List.join [doubledash, boundary] - endMarker = List.join [newline, doubledash, boundary, doubledash, newline] - boundaryWithPrefix = List.join [newline, doubledash, boundary] + start_marker = List.join([doubledash, boundary]) + end_marker = List.join([newline, doubledash, boundary, doubledash, newline]) + boundary_with_prefix = List.join([newline, doubledash, boundary]) - isEnclosedByBoundary = - List.startsWith body startMarker - && List.endsWith body endMarker + is_enclosed_by_boundary = + List.starts_with(body, start_marker) + && List.ends_with(body, end_marker) - if isEnclosedByBoundary then + if is_enclosed_by_boundary then body - |> List.dropFirst (List.len startMarker) - |> SplitList.split_on_list boundaryWithPrefix - |> List.dropIf \part -> part == doubledash - |> List.keepOks parseAllHeaders + |> List.drop_first(List.len(start_marker)) + |> SplitList.split_on_list(boundary_with_prefix) + |> List.drop_if(\part -> part == doubledash) + |> List.keep_oks(parse_all_headers) |> Ok else - Err ExpectedEnclosedByBoundary + Err(ExpectedEnclosedByBoundary) expect # """ @@ -240,57 +267,67 @@ expect # ------WebKitFormBoundaryQGx5ri4XFwWbaAX4-- # # """ - actual = parse_form_data { - body: [45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, 102, 105, 108, 101, 34, 59, 32, 102, 105, 108, 101, 110, 97, 109, 101, 61, 34, 115, 97, 109, 112, 108, 101, 50, 46, 99, 115, 118, 34, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 116, 101, 120, 116, 47, 99, 115, 118, 13, 10, 13, 10, 70, 105, 114, 115, 116, 44, 76, 97, 115, 116, 13, 10, 82, 97, 99, 104, 101, 108, 44, 66, 111, 111, 107, 101, 114, 13, 10, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55, 45, 45, 13, 10], - boundary: [45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55], - } - # Result.isErr actual - expected = Ok [ + actual = parse_form_data( { - data: [70, 105, 114, 115, 116, 44, 76, 97, 115, 116, 13, 10, 82, 97, 99, 104, 101, 108, 44, 66, 111, 111, 107, 101, 114, 13, 10], - disposition: [32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, 102, 105, 108, 101, 34, 59, 32, 102, 105, 108, 101, 110, 97, 109, 101, 61, 34, 115, 97, 109, 112, 108, 101, 50, 46, 99, 115, 118, 34], - encoding: [], - type: [32, 116, 101, 120, 116, 47, 99, 115, 118], + body: [45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, 102, 105, 108, 101, 34, 59, 32, 102, 105, 108, 101, 110, 97, 109, 101, 61, 34, 115, 97, 109, 112, 108, 101, 50, 46, 99, 115, 118, 34, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32, 116, 101, 120, 116, 47, 99, 115, 118, 13, 10, 13, 10, 70, 105, 114, 115, 116, 44, 76, 97, 115, 116, 13, 10, 82, 97, 99, 104, 101, 108, 44, 66, 111, 111, 107, 101, 114, 13, 10, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55, 45, 45, 13, 10], + boundary: [45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 70, 71, 54, 83, 65, 109, 121, 106, 112, 49, 108, 118, 102, 57, 80, 55], }, - ] + ) + # Result.isErr actual + expected = Ok( + [ + { + data: [70, 105, 114, 115, 116, 44, 76, 97, 115, 116, 13, 10, 82, 97, 99, 104, 101, 108, 44, 66, 111, 111, 107, 101, 114, 13, 10], + disposition: [32, 102, 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, 102, 105, 108, 101, 34, 59, 32, 102, 105, 108, 101, 110, 97, 109, 101, 61, 34, 115, 97, 109, 112, 108, 101, 50, 46, 99, 115, 118, 34], + encoding: [], + type: [32, 116, 101, 120, 116, 47, 99, 115, 118], + }, + ], + ) actual == expected expect - input = Str.toUtf8 "--12345\r\nContent-Disposition: form-data; name=\"sometext\"\r\n\r\nsome text sent via post...\r\n--12345--\r\n" - actual = parse_form_data { - body: input, - boundary: Str.toUtf8 "12345", - } - expected = Ok [ + input = Str.to_utf8("--12345\r\nContent-Disposition: form-data; name=\"sometext\"\r\n\r\nsome text sent via post...\r\n--12345--\r\n") + actual = parse_form_data( { - disposition: Str.toUtf8 " form-data; name=\"sometext\"", - type: [], - encoding: [], - data: Str.toUtf8 "some text sent via post...", + body: input, + boundary: Str.to_utf8("12345"), }, - ] + ) + expected = Ok( + [ + { + disposition: Str.to_utf8(" form-data; name=\"sometext\""), + type: [], + encoding: [], + data: Str.to_utf8("some text sent via post..."), + }, + ], + ) actual == expected expect - body = Str.toUtf8 "--AaB03x\r\nContent-Disposition: form-data; name=\"submit-name\"\r\n\r\nLarry\r\n--AaB03x\r\nContent-Disposition: form-data; name=\"files\"\r\nContent-Type: multipart/mixed; boundary=BbC04y\r\n\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\n\r\n... contents of file1.txt ...\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file2.gif\"\r\nContent-Type: image/gif\r\nContent-Transfer-Encoding: binary\r\n\r\n...contents of file2.gif...\r\n--BbC04y--\r\n--AaB03x--\r\n" - boundary = Str.toUtf8 "AaB03x" - actual = parse_form_data { body, boundary } - expected = Ok [ - { - disposition: Str.toUtf8 " form-data; name=\"submit-name\"", - type: [], - encoding: [], - data: Str.toUtf8 "Larry", - }, - { - disposition: Str.toUtf8 " form-data; name=\"files\"", - type: Str.toUtf8 " multipart/mixed; boundary=BbC04y", - encoding: [], - data: Str.toUtf8 "--BbC04y\r\nContent-Disposition: file; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\n\r\n... contents of file1.txt ...\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file2.gif\"\r\nContent-Type: image/gif\r\nContent-Transfer-Encoding: binary\r\n\r\n...contents of file2.gif...\r\n--BbC04y--", - }, - ] + body = Str.to_utf8("--AaB03x\r\nContent-Disposition: form-data; name=\"submit-name\"\r\n\r\nLarry\r\n--AaB03x\r\nContent-Disposition: form-data; name=\"files\"\r\nContent-Type: multipart/mixed; boundary=BbC04y\r\n\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\n\r\n... contents of file1.txt ...\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file2.gif\"\r\nContent-Type: image/gif\r\nContent-Transfer-Encoding: binary\r\n\r\n...contents of file2.gif...\r\n--BbC04y--\r\n--AaB03x--\r\n") + boundary = Str.to_utf8("AaB03x") + actual = parse_form_data({ body, boundary }) + expected = Ok( + [ + { + disposition: Str.to_utf8(" form-data; name=\"submit-name\""), + type: [], + encoding: [], + data: Str.to_utf8("Larry"), + }, + { + disposition: Str.to_utf8(" form-data; name=\"files\""), + type: Str.to_utf8(" multipart/mixed; boundary=BbC04y"), + encoding: [], + data: Str.to_utf8("--BbC04y\r\nContent-Disposition: file; filename=\"file1.txt\"\r\nContent-Type: text/plain\r\n\r\n... contents of file1.txt ...\r\n--BbC04y\r\nContent-Disposition: file; filename=\"file2.gif\"\r\nContent-Type: image/gif\r\nContent-Transfer-Encoding: binary\r\n\r\n...contents of file2.gif...\r\n--BbC04y--"), + }, + ], + ) actual == expected @@ -306,75 +343,83 @@ expect parse_form_url_encoded : List U8 -> Result (Dict Str Str) [BadUtf8] parse_form_url_encoded = \bytes -> - chainUtf8 = \bytesList, tryFun -> Str.fromUtf8 bytesList |> mapUtf8Err |> Result.try tryFun + chain_utf8 = \bytes_list, try_fun -> Str.from_utf8(bytes_list) |> map_utf8_err |> Result.try(try_fun) # simplify `BadUtf8 Utf8ByteProblem ...` error - mapUtf8Err = \err -> err |> Result.mapErr \_ -> BadUtf8 + map_utf8_err = \err -> err |> Result.map_err(\_ -> BadUtf8) - help = \bytesRemaining, state, key, chomped, dict -> - tail = List.dropFirst bytesRemaining 1 + help = \bytes_remaining, state, key, chomped, dict -> + tail = List.drop_first(bytes_remaining, 1) - when bytesRemaining is - [] if List.isEmpty chomped -> dict |> Ok + when bytes_remaining is + [] if List.is_empty(chomped) -> dict |> Ok [] -> # chomped last value key - |> chainUtf8 \keyStr -> - chomped - |> chainUtf8 \valueStr -> - Dict.insert dict keyStr valueStr |> Ok - - ['=', ..] -> help tail ParsingValue chomped [] dict # put chomped into key + |> chain_utf8( + \key_str -> + chomped + |> chain_utf8( + \value_str -> + Dict.insert(dict, key_str, value_str) |> Ok, + ), + ) + + ['=', ..] -> help(tail, ParsingValue, chomped, [], dict) # put chomped into key ['&', ..] -> key - |> chainUtf8 \keyStr -> - chomped - |> chainUtf8 \valueStr -> - help tail ParsingKey [] [] (Dict.insert dict keyStr valueStr) + |> chain_utf8( + \key_str -> + chomped + |> chain_utf8( + \value_str -> + help(tail, ParsingKey, [], [], Dict.insert(dict, key_str, value_str)), + ), + ) - ['%', secondByte, thirdByte, ..] -> - hex = Num.toU8 (hexBytesToU32 [secondByte, thirdByte]) + ['%', second_byte, third_byte, ..] -> + hex = Num.to_u8(hex_bytes_to_u32([second_byte, third_byte])) - help (List.dropFirst tail 2) state key (List.append chomped hex) dict + help(List.drop_first(tail, 2), state, key, List.append(chomped, hex), dict) - [firstByte, ..] -> help tail state key (List.append chomped firstByte) dict + [first_byte, ..] -> help(tail, state, key, List.append(chomped, first_byte), dict) - help bytes ParsingKey [] [] (Dict.empty {}) + help(bytes, ParsingKey, [], [], Dict.empty({})) -expect hexBytesToU32 ['2', '0'] == 32 +expect hex_bytes_to_u32(['2', '0']) == 32 expect - bytes = Str.toUtf8 "todo=foo&status=bar" - parsed = parse_form_url_encoded bytes |> Result.withDefault (Dict.empty {}) + bytes = Str.to_utf8("todo=foo&status=bar") + parsed = parse_form_url_encoded(bytes) |> Result.with_default(Dict.empty({})) - Dict.toList parsed == [("todo", "foo"), ("status", "bar")] + Dict.to_list(parsed) == [("todo", "foo"), ("status", "bar")] expect - Str.toUtf8 "task=asdfs%20adf&status=qwerwe" + Str.to_utf8("task=asdfs%20adf&status=qwerwe") |> parse_form_url_encoded - |> Result.withDefault (Dict.empty {}) - |> Dict.toList - |> Bool.isEq [("task", "asdfs adf"), ("status", "qwerwe")] + |> Result.with_default(Dict.empty({})) + |> Dict.to_list + |> Bool.is_eq([("task", "asdfs adf"), ("status", "qwerwe")]) -hexBytesToU32 : List U8 -> U32 -hexBytesToU32 = \bytes -> +hex_bytes_to_u32 : List U8 -> U32 +hex_bytes_to_u32 = \bytes -> bytes |> List.reverse - |> List.walkWithIndex 0 \accum, byte, i -> accum + (Num.powInt 16 (Num.toU32 i)) * (hexToDec byte) - |> Num.toU32 - -expect hexBytesToU32 ['0', '0', '0', '0'] == 0 -expect hexBytesToU32 ['0', '0', '0', '1'] == 1 -expect hexBytesToU32 ['0', '0', '0', 'F'] == 15 -expect hexBytesToU32 ['0', '0', '1', '0'] == 16 -expect hexBytesToU32 ['0', '0', 'F', 'F'] == 255 -expect hexBytesToU32 ['0', '1', '0', '0'] == 256 -expect hexBytesToU32 ['0', 'F', 'F', 'F'] == 4095 -expect hexBytesToU32 ['1', '0', '0', '0'] == 4096 -expect hexBytesToU32 ['1', '6', 'F', 'F', '1'] == 94193 - -hexToDec : U8 -> U32 -hexToDec = \byte -> + |> List.walk_with_index(0, \accum, byte, i -> accum + (Num.pow_int(16, Num.to_u32(i))) * (hex_to_dec(byte))) + |> Num.to_u32 + +expect hex_bytes_to_u32(['0', '0', '0', '0']) == 0 +expect hex_bytes_to_u32(['0', '0', '0', '1']) == 1 +expect hex_bytes_to_u32(['0', '0', '0', 'F']) == 15 +expect hex_bytes_to_u32(['0', '0', '1', '0']) == 16 +expect hex_bytes_to_u32(['0', '0', 'F', 'F']) == 255 +expect hex_bytes_to_u32(['0', '1', '0', '0']) == 256 +expect hex_bytes_to_u32(['0', 'F', 'F', 'F']) == 4095 +expect hex_bytes_to_u32(['1', '0', '0', '0']) == 4096 +expect hex_bytes_to_u32(['1', '6', 'F', 'F', '1']) == 94193 + +hex_to_dec : U8 -> U32 +hex_to_dec = \byte -> when byte is '0' -> 0 '1' -> 1 @@ -392,10 +437,10 @@ hexToDec = \byte -> 'D' -> 13 'E' -> 14 'F' -> 15 - _ -> crash "Impossible error: the `when` block I'm in should have matched before reaching the catch-all `_`." + _ -> crash("Impossible error: the `when` block I'm in should have matched before reaching the catch-all `_`.") -expect hexToDec '0' == 0 -expect hexToDec 'F' == 15 +expect hex_to_dec('0') == 0 +expect hex_to_dec('F') == 15 ## For HTML forms that include files or large amounts of text. ## @@ -407,19 +452,23 @@ parse_multipart_form_data : } -> Result (List MultipartFormData.FormData) [InvalidMultipartFormData, ExpectedContentTypeHeader, InvalidContentTypeHeader] parse_multipart_form_data = \args -> - decode_multipart_form_data_boundary args.headers - |> Result.try \boundary -> - { body: args.body, boundary } - |> parse_form_data - |> Result.mapErr \_ -> InvalidMultipartFormData + decode_multipart_form_data_boundary(args.headers) + |> Result.try( + \boundary -> + { body: args.body, boundary } + |> parse_form_data + |> Result.map_err(\_ -> InvalidMultipartFormData), + ) decode_multipart_form_data_boundary : List { name : Str, value : Str } -> Result (List U8) _ decode_multipart_form_data_boundary = \headers -> headers - |> List.keepIf \{ name } -> name == "Content-Type" || name == "content-type" + |> List.keep_if(\{ name } -> name == "Content-Type" || name == "content-type") |> List.first - |> Result.mapErr \ListWasEmpty -> ExpectedContentTypeHeader - |> Result.try \{ value } -> - when Str.splitLast value "=" is - Ok { after } -> Ok (Str.toUtf8 after) - Err NotFound -> Err InvalidContentTypeHeader + |> Result.map_err(\ListWasEmpty -> ExpectedContentTypeHeader) + |> Result.try( + \{ value } -> + when Str.split_last(value, "=") is + Ok({ after }) -> Ok(Str.to_utf8(after)) + Err(NotFound) -> Err(InvalidContentTypeHeader), + ) diff --git a/platform/Path.roc b/platform/Path.roc index d9ff10ce..709b6424 100644 --- a/platform/Path.roc +++ b/platform/Path.roc @@ -69,10 +69,10 @@ IOErr : InternalIOErr.IOErr ## > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. write! : val, Path, fmt => Result {} [FileWriteErr Path IOErr] where val implements Encoding, fmt implements EncoderFormatting write! = \val, path, fmt -> - bytes = Encode.toBytes val fmt + bytes = Encode.to_bytes(val, fmt) # TODO handle encoding errors here, once they exist - write_bytes! bytes path + write_bytes!(bytes, path) ## Writes bytes to a file. ## @@ -86,10 +86,10 @@ write! = \val, path, fmt -> ## > To format data before writing it to a file, you can use [Path.write!] instead. write_bytes! : List U8, Path => Result {} [FileWriteErr Path IOErr] write_bytes! = \bytes, path -> - path_bytes = InternalPath.to_bytes path + path_bytes = InternalPath.to_bytes(path) - Host.file_write_bytes! path_bytes bytes - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_write_bytes!(path_bytes, bytes) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## @@ -103,10 +103,10 @@ write_bytes! = \bytes, path -> ## > To write unformatted bytes to a file, you can use [Path.write_bytes!] instead. write_utf8! : Str, Path => Result {} [FileWriteErr Path IOErr] write_utf8! = \str, path -> - path_bytes = InternalPath.to_bytes path + path_bytes = InternalPath.to_bytes(path) - Host.file_write_utf8! path_bytes str - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_write_utf8!(path_bytes, str) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Note that the path may not be valid depending on the filesystem where it is used. ## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not @@ -119,7 +119,7 @@ write_utf8! = \str, path -> ## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.). from_str : Str -> Path from_str = \str -> - FromStr str + FromStr(str) |> InternalPath.wrap ## Not all filesystems use Unicode paths. This function can be used to create a path which @@ -129,7 +129,7 @@ from_str = \str -> ## (e.g. `Path.read_bytes` or `WriteStream.openPath`) will fail. from_bytes : List U8 -> Path from_bytes = \bytes -> - ArbitraryBytes bytes + ArbitraryBytes(bytes) |> InternalPath.wrap ## Unfortunately, operating system paths do not include information about which charset @@ -165,13 +165,13 @@ from_bytes = \bytes -> ## `toStrUsingCharset` instead of [display]. display : Path -> Str display = \path -> - when InternalPath.unwrap path is - FromStr str -> str - FromOperatingSystem bytes | ArbitraryBytes bytes -> - when Str.fromUtf8 bytes is - Ok str -> str + when InternalPath.unwrap(path) is + FromStr(str) -> str + FromOperatingSystem(bytes) | ArbitraryBytes(bytes) -> + when Str.from_utf8(bytes) is + Ok(str) -> str # TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet! - Err _ -> "οΏ½" + Err(_) -> "οΏ½" ## Returns true if the path exists on disk and is pointing at a directory. ## Returns `Ok false` if the path exists and it is not a directory. If the path does not exist, @@ -182,8 +182,8 @@ display = \path -> ## > [`File.is_dir`](File#is_dir!) does the same thing, except it takes a [Str] instead of a [Path]. is_dir! : Path => Result Bool [PathErr IOErr] is_dir! = \path -> - res = type!? path - Ok (res == IsDir) + res = type!(path)? + Ok((res == IsDir)) ## Returns true if the path exists on disk and is pointing at a regular file. ## Returns `Ok false` if the path exists and it is not a file. If the path does not exist, @@ -194,8 +194,8 @@ is_dir! = \path -> ## > [`File.is_file`](File#is_file!) does the same thing, except it takes a [Str] instead of a [Path]. is_file! : Path => Result Bool [PathErr IOErr] is_file! = \path -> - res = type!? path - Ok (res == IsFile) + res = type!(path)? + Ok((res == IsFile)) ## Returns true if the path exists on disk and is pointing at a symbolic link. ## Returns `Ok false` if the path exists and it is not a symbolic link. If the path does not exist, @@ -206,23 +206,25 @@ is_file! = \path -> ## > [`File.is_sym_link`](File#is_sym_link!) does the same thing, except it takes a [Str] instead of a [Path]. is_sym_link! : Path => Result Bool [PathErr IOErr] is_sym_link! = \path -> - res = type!? path - Ok (res == IsSymLink) + res = type!(path)? + Ok((res == IsSymLink)) ## Return the type of the path if the path exists on disk. ## ## > [`File.type`](File#type!) does the same thing, except it takes a [Str] instead of a [Path]. type! : Path => Result [IsFile, IsDir, IsSymLink] [PathErr IOErr] type! = \path -> - Host.path_type! (InternalPath.to_bytes path) - |> Result.mapErr \err -> PathErr (InternalIOErr.handle_err err) - |> Result.map \path_type -> - if path_type.is_sym_link then - IsSymLink - else if path_type.is_dir then - IsDir - else - IsFile + Host.path_type!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> PathErr(InternalIOErr.handle_err(err))) + |> Result.map( + \path_type -> + if path_type.is_sym_link then + IsSymLink + else if path_type.is_dir then + IsDir + else + IsFile, + ) ## If the last component of this path has no `.`, appends `.` followed by the given string. ## Otherwise, replaces everything after the last `.` with the given string. @@ -235,30 +237,30 @@ type! = \path -> ## ``` with_extension : Path, Str -> Path with_extension = \path, extension -> - when InternalPath.unwrap path is - FromOperatingSystem bytes | ArbitraryBytes bytes -> + when InternalPath.unwrap(path) is + FromOperatingSystem(bytes) | ArbitraryBytes(bytes) -> before_dot = - when List.splitLast bytes (Num.toU8 '.') is - Ok { before } -> before - Err NotFound -> bytes + when List.split_last(bytes, Num.to_u8('.')) is + Ok({ before }) -> before + Err(NotFound) -> bytes before_dot - |> List.reserve (Str.countUtf8Bytes extension |> Num.intCast |> Num.addSaturated 1) - |> List.append (Num.toU8 '.') - |> List.concat (Str.toUtf8 extension) + |> List.reserve((Str.count_utf8_bytes(extension) |> Num.int_cast |> Num.add_saturated(1))) + |> List.append(Num.to_u8('.')) + |> List.concat(Str.to_utf8(extension)) |> ArbitraryBytes |> InternalPath.wrap - FromStr str -> + FromStr(str) -> before_dot = - when Str.splitLast str "." is - Ok { before } -> before - Err NotFound -> str + when Str.split_last(str, ".") is + Ok({ before }) -> before + Err(NotFound) -> str before_dot - |> Str.reserve (Str.countUtf8Bytes extension |> Num.addSaturated 1) - |> Str.concat "." - |> Str.concat extension + |> Str.reserve((Str.count_utf8_bytes(extension) |> Num.add_saturated(1))) + |> Str.concat(".") + |> Str.concat(extension) |> FromStr |> InternalPath.wrap @@ -284,8 +286,8 @@ with_extension = \path, extension -> ## > [`File.delete`](File#delete!) does the same thing, except it takes a [Str] instead of a [Path]. delete! : Path => Result {} [FileWriteErr Path IOErr] delete! = \path -> - Host.file_delete! (InternalPath.to_bytes path) - |> Result.mapErr \err -> FileWriteErr path (InternalIOErr.handle_err err) + Host.file_delete!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> FileWriteErr(path, InternalIOErr.handle_err(err))) ## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text. ## @@ -303,11 +305,11 @@ delete! = \path -> read_utf8! : Path => Result Str [FileReadErr Path IOErr, FileReadUtf8Err Path _] read_utf8! = \path -> bytes = - Host.file_read_bytes! (InternalPath.to_bytes path) - |> Result.mapErr? \read_err -> FileReadErr path (InternalIOErr.handle_err read_err) + Host.file_read_bytes!(InternalPath.to_bytes(path)) + |> Result.map_err?(\read_err -> FileReadErr(path, InternalIOErr.handle_err(read_err))) - Str.fromUtf8 bytes - |> Result.mapErr \err -> FileReadUtf8Err path err + Str.from_utf8(bytes) + |> Result.map_err(\err -> FileReadUtf8Err(path, err)) ## Reads all the bytes in a file. ## @@ -323,17 +325,17 @@ read_utf8! = \path -> ## > [`File.read_bytes`](File#read_bytes!) does the same thing, except it takes a [Str] instead of a [Path]. read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr] read_bytes! = \path -> - Host.file_read_bytes! (InternalPath.to_bytes path) - |> Result.mapErr \err -> FileReadErr path (InternalIOErr.handle_err err) + Host.file_read_bytes!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> FileReadErr(path, InternalIOErr.handle_err(err))) ## Lists the files and directories inside the directory. ## ## > [`Dir.list`](Dir#list!) does the same thing, except it takes a [Str] instead of a [Path]. list_dir! : Path => Result (List Path) [DirErr IOErr] list_dir! = \path -> - when Host.dir_list! (InternalPath.to_bytes path) is - Ok entries -> Ok (List.map entries InternalPath.from_os_bytes) - Err err -> Err (DirErr (InternalIOErr.handle_err err)) + when Host.dir_list!(InternalPath.to_bytes(path)) is + Ok(entries) -> Ok(List.map(entries, InternalPath.from_os_bytes)) + Err(err) -> Err(DirErr(InternalIOErr.handle_err(err))) ## Deletes a directory if it's empty ## @@ -346,8 +348,8 @@ list_dir! = \path -> ## > [`Dir.delete_empty`](Dir#delete_empty!) does the same thing, except it takes a [Str] instead of a [Path]. delete_empty! : Path => Result {} [DirErr IOErr] delete_empty! = \path -> - Host.dir_delete_empty! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_delete_empty!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Recursively deletes a directory as well as all files and directories ## inside it. @@ -361,8 +363,8 @@ delete_empty! = \path -> ## > [`Dir.delete_all`](Dir#delete_all!) does the same thing, except it takes a [Str] instead of a [Path]. delete_all! : Path => Result {} [DirErr IOErr] delete_all! = \path -> - Host.dir_delete_all! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_delete_all!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a directory ## @@ -374,8 +376,8 @@ delete_all! = \path -> ## > [`Dir.create`](Dir#create!) does the same thing, except it takes a [Str] instead of a [Path]. create_dir! : Path => Result {} [DirErr IOErr] create_dir! = \path -> - Host.dir_create! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_create!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a directory recursively adding any missing parent directories. ## @@ -386,8 +388,8 @@ create_dir! = \path -> ## > [`Dir.create_all`](Dir#create_all!) does the same thing, except it takes a [Str] instead of a [Path]. create_all! : Path => Result {} [DirErr IOErr] create_all! = \path -> - Host.dir_create_all! (InternalPath.to_bytes path) - |> Result.mapErr \err -> DirErr (InternalIOErr.handle_err err) + Host.dir_create_all!(InternalPath.to_bytes(path)) + |> Result.map_err(\err -> DirErr(InternalIOErr.handle_err(err))) ## Creates a new hard link on the filesystem. ## @@ -399,6 +401,6 @@ create_all! = \path -> ## > [File.hard_link!] does the same thing, except it takes a [Str] instead of a [Path]. hard_link! : Path => Result {} [LinkErr IOErr] hard_link! = \path -> - Host.hard_link! (InternalPath.to_bytes path) - |> Result.mapErr InternalIOErr.handle_err - |> Result.mapErr LinkErr + Host.hard_link!(InternalPath.to_bytes(path)) + |> Result.map_err(InternalIOErr.handle_err) + |> Result.map_err(LinkErr) diff --git a/platform/SplitList.roc b/platform/SplitList.roc index 7261409e..51d0aca2 100644 --- a/platform/SplitList.roc +++ b/platform/SplitList.roc @@ -15,13 +15,13 @@ split_on_list : List a, List a -> List (List a) where a implements Eq split_on_list = \input_list, separator -> # reserve capacity for markers which mark split boundaries - init_markers = List.withCapacity 100 + init_markers = List.with_capacity(100) # find all the start and stop markers - markers = List.walkWithIndex input_list init_markers (walk_help_find_starts input_list separator) + markers = List.walk_with_index(input_list, init_markers, walk_help_find_starts(input_list, separator)) # split the input based on the markers - walk_split_help input_list markers + walk_split_help(input_list, markers) # produces a Stop, followed by a sequence of Start, Stop, Start, Stop, ... walk_help_find_starts = \input_list, separator_list -> @@ -30,12 +30,12 @@ walk_help_find_starts = \input_list, separator_list -> else \all_markers, _, idx -> - len = List.len separator_list + len = List.len(separator_list) - if List.sublist input_list { start: idx, len } == separator_list then + if List.sublist(input_list, { start: idx, len }) == separator_list then all_markers - |> List.append (Stop idx) - |> List.append (Start (idx + len)) + |> List.append(Stop(idx)) + |> List.append(Start((idx + len))) else all_markers @@ -43,8 +43,8 @@ walk_help_find_starts = \input_list, separator_list -> expect input = [] separator = [1, 2, 3] - help = walk_help_find_starts input separator - actual = List.walkWithIndex input [] help + help = walk_help_find_starts(input, separator) + actual = List.walk_with_index(input, [], help) expected = [] actual == expected @@ -52,8 +52,8 @@ expect expect input = [1, 2, 3] separator = [] - help = walk_help_find_starts input separator - actual = List.walkWithIndex input [] help + help = walk_help_find_starts(input, separator) + actual = List.walk_with_index(input, [], help) expected = [] actual == expected @@ -61,27 +61,27 @@ expect expect input = [3, 4, 5, 6, 7, 8] separator = [3, 4, 5] - help = walk_help_find_starts input separator - actual = List.walkWithIndex input [] help - expected = [Stop 0, Start 3] + help = walk_help_find_starts(input, separator) + actual = List.walk_with_index(input, [], help) + expected = [Stop(0), Start(3)] actual == expected # multiple separators in the middle expect input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10] separator = [3, 4, 5] - help = walk_help_find_starts input separator - actual = List.walkWithIndex input [] help - expected = [Stop 2, Start 5, Stop 10, Start 13] + help = walk_help_find_starts(input, separator) + actual = List.walk_with_index(input, [], help) + expected = [Stop(2), Start(5), Stop(10), Start(13)] actual == expected # separator at end expect input = [6, 7, 8, 3, 4, 5] separator = [3, 4, 5] - help = walk_help_find_starts input separator - actual = List.walkWithIndex input [] help - expected = [Stop 3, Start 6] + help = walk_help_find_starts(input, separator) + actual = List.walk_with_index(input, [], help) + expected = [Stop(3), Start(6)] actual == expected walk_split_help : List a, List [Start U64, Stop U64] -> List (List a) where a implements Eq @@ -89,58 +89,58 @@ walk_split_help = \input, markers -> go = \remaining_markers, state -> when remaining_markers is [] -> state - [Stop stop, .. as rest] if stop == 0 -> go rest state - [Stop stop, .. as rest] -> - go rest (List.append state (List.sublist input { start: 0, len: stop })) + [Stop(stop), .. as rest] if stop == 0 -> go(rest, state) + [Stop(stop), .. as rest] -> + go(rest, List.append(state, List.sublist(input, { start: 0, len: stop }))) - [Start start, Stop stop, .. as rest] -> - go rest (List.append state (List.sublist input { start, len: stop - start })) + [Start(start), Stop(stop), .. as rest] -> + go(rest, List.append(state, List.sublist(input, { start, len: stop - start }))) - [Start start] if start >= List.len input -> state - [Start start] -> - List.append state (List.sublist input { start, len: ((List.len input) - start) }) + [Start(start)] if start >= List.len(input) -> state + [Start(start)] -> + List.append(state, List.sublist(input, { start, len: ((List.len(input)) - start) })) - _ -> crash "Unreachable:\n\tThis list should have matched earlier when branches: $(Inspect.toStr remaining_markers)" + _ -> crash("Unreachable:\n\tThis list should have matched earlier when branches: $(Inspect.to_str(remaining_markers))") - go markers [] + go(markers, []) expect - actual = walk_split_help [1, 2, 3, 5, 6, 7, 8, 9, 10] [Stop 2] + actual = walk_split_help([1, 2, 3, 5, 6, 7, 8, 9, 10], [Stop(2)]) expected = [[1, 2]] actual == expected expect input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10] - actual = walk_split_help input [Stop 2, Start 5, Stop 10, Start 13] + actual = walk_split_help(input, [Stop(2), Start(5), Stop(10), Start(13)]) expected = [[1, 2], [6, 7, 8, 9, 10], [6, 7, 8, 9, 10]] actual == expected expect input = [1, 2, 3, 4, 5, 6, 7, 3, 4, 0, 0] - actual = split_on_list input [3, 4] + actual = split_on_list(input, [3, 4]) expected = [[1, 2], [5, 6, 7], [0, 0]] actual == expected expect input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 4, 5, 6, 7, 8, 9, 10] - actual = split_on_list input [3, 4, 5] + actual = split_on_list(input, [3, 4, 5]) expected = [[1, 2], [6, 7, 8, 9, 10], [6, 7, 8, 9, 10]] actual == expected expect input = [One, Two, Three, Four, Five, Six, Seven, Eight, One, Two, Nine, Ten, Three, Four, Five, Six, Seven, One, Two, Eight, Nine, Ten] - actual = split_on_list input [One, Two] + actual = split_on_list(input, [One, Two]) expected = [[Three, Four, Five, Six, Seven, Eight], [Nine, Ten, Three, Four, Five, Six, Seven], [Eight, Nine, Ten]] actual == expected expect input = [6, 7, 8, 3, 4, 5] - actual = split_on_list input [3, 4, 5] + actual = split_on_list(input, [3, 4, 5]) expected = [[6, 7, 8]] actual == expected expect input = [3, 4, 5, 6, 7, 8] - actual = split_on_list input [3, 4, 5] + actual = split_on_list(input, [3, 4, 5]) expected = [[6, 7, 8]] actual == expected diff --git a/platform/Sqlite.roc b/platform/Sqlite.roc index dfe044f0..b596b144 100644 --- a/platform/Sqlite.roc +++ b/platform/Sqlite.roc @@ -166,38 +166,38 @@ prepare! : } => Result Stmt [SqliteErr ErrCode Str] prepare! = \{ path, query: q } -> - Host.sqlite_prepare! path q - |> Result.map @Stmt - |> Result.mapErr internal_to_external_error + Host.sqlite_prepare!(path, q) + |> Result.map(@Stmt) + |> Result.map_err(internal_to_external_error) # internal use only bind! : Stmt, List Binding => Result {} [SqliteErr ErrCode Str] -bind! = \@Stmt stmt, bindings -> - Host.sqlite_bind! stmt bindings - |> Result.mapErr internal_to_external_error +bind! = \@Stmt(stmt), bindings -> + Host.sqlite_bind!(stmt, bindings) + |> Result.map_err(internal_to_external_error) # internal use only columns! : Stmt => List Str -columns! = \@Stmt stmt -> - Host.sqlite_columns! stmt +columns! = \@Stmt(stmt) -> + Host.sqlite_columns!(stmt) # internal use only column_value! : Stmt, U64 => Result Value [SqliteErr ErrCode Str] -column_value! = \@Stmt stmt, i -> - Host.sqlite_column_value! stmt i - |> Result.mapErr internal_to_external_error +column_value! = \@Stmt(stmt), i -> + Host.sqlite_column_value!(stmt, i) + |> Result.map_err(internal_to_external_error) # internal use only step! : Stmt => Result [Row, Done] [SqliteErr ErrCode Str] -step! = \@Stmt stmt -> - Host.sqlite_step! stmt - |> Result.mapErr internal_to_external_error +step! = \@Stmt(stmt) -> + Host.sqlite_step!(stmt) + |> Result.map_err(internal_to_external_error) # internal use only reset! : Stmt => Result {} [SqliteErr ErrCode Str] -reset! = \@Stmt stmt -> - Host.sqlite_reset! stmt - |> Result.mapErr internal_to_external_error +reset! = \@Stmt(stmt) -> + Host.sqlite_reset!(stmt) + |> Result.map_err(internal_to_external_error) ## Execute a SQL statement that doesn't return any rows (like INSERT, UPDATE, DELETE). ## @@ -220,8 +220,8 @@ execute! : } => Result {} [SqliteErr ErrCode Str, UnhandledRows] execute! = \{ path, query: q, bindings } -> - stmt = try prepare! { path, query: q } - execute_prepared! { stmt, bindings } + stmt = try(prepare!, { path, query: q }) + execute_prepared!({ stmt, bindings }) ## Execute a prepared SQL statement that doesn't return any rows. ## @@ -234,18 +234,18 @@ execute_prepared! : } => Result {} [SqliteErr ErrCode Str, UnhandledRows] execute_prepared! = \{ stmt, bindings } -> - try bind! stmt bindings - res = step! stmt - try reset! stmt + try(bind!, stmt, bindings) + res = step!(stmt) + try(reset!, stmt) when res is - Ok Done -> - Ok {} + Ok(Done) -> + Ok({}) - Ok Row -> - Err UnhandledRows + Ok(Row) -> + Err(UnhandledRows) - Err e -> - Err e + Err(e) -> + Err(e) ## Execute a SQL query and decode exactly one row into a value. ## @@ -268,8 +268,8 @@ query! : } => Result a (SqlDecodeErr (RowCountErr err)) query! = \{ path, query: q, bindings, row } -> - stmt = try prepare! { path, query: q } - query_prepared! { stmt, bindings, row } + stmt = try(prepare!, { path, query: q }) + query_prepared!({ stmt, bindings, row }) ## Execute a prepared SQL query and decode exactly one row into a value. ## @@ -283,9 +283,9 @@ query_prepared! : } => Result a (SqlDecodeErr (RowCountErr err)) query_prepared! = \{ stmt, bindings, row: decode } -> - try bind! stmt bindings - res = decode_exactly_one_row! stmt decode - try reset! stmt + try(bind!, stmt, bindings) + res = decode_exactly_one_row!(stmt, decode) + try(reset!, stmt) res ## Execute a SQL query and decode multiple rows into a list of values. @@ -311,8 +311,8 @@ query_many! : } => Result (List a) (SqlDecodeErr err) query_many! = \{ path, query: q, bindings, rows } -> - stmt = try prepare! { path, query: q } - query_many_prepared! { stmt, bindings, rows } + stmt = try(prepare!, { path, query: q }) + query_many_prepared!({ stmt, bindings, rows }) ## Execute a prepared SQL query and decode multiple rows into a list of values. ## @@ -326,9 +326,9 @@ query_many_prepared! : } => Result (List a) (SqlDecodeErr err) query_many_prepared! = \{ stmt, bindings, rows: decode } -> - try bind! stmt bindings - res = decode_rows! stmt decode - try reset! stmt + try(bind!, stmt, bindings) + res = decode_rows!(stmt, decode) + try(reset!, stmt) res SqlDecodeErr err : [FieldNotFound Str, SqliteErr ErrCode Str]err @@ -344,15 +344,17 @@ SqlDecode a err := List Str -> (Stmt => Result a (SqlDecodeErr err)) ## } ## ``` decode_record : SqlDecode a err, SqlDecode b err, (a, b -> c) -> SqlDecode c err -decode_record = \@SqlDecode gen_first, @SqlDecode gen_second, mapper -> - @SqlDecode \cols -> - decode_first! = gen_first cols - decode_second! = gen_second cols - - \stmt -> - first = try decode_first! stmt - second = try decode_second! stmt - Ok (mapper first second) +decode_record = \@SqlDecode(gen_first), @SqlDecode(gen_second), mapper -> + @SqlDecode( + \cols -> + decode_first! = gen_first(cols) + decode_second! = gen_second(cols) + + \stmt -> + first = try(decode_first!, stmt) + second = try(decode_second!, stmt) + Ok(mapper(first, second)), + ) ## Transform the output of a decoder by applying a function to the decoded value. ## @@ -361,69 +363,74 @@ decode_record = \@SqlDecode gen_first, @SqlDecode gen_second, mapper -> ## Sqlite.i64 "id" |> Sqlite.map_value Num.toStr ## ``` map_value : SqlDecode a err, (a -> b) -> SqlDecode b err -map_value = \@SqlDecode gen_decode, mapper -> - @SqlDecode \cols -> - decode! = gen_decode cols +map_value = \@SqlDecode(gen_decode), mapper -> + @SqlDecode( + \cols -> + decode! = gen_decode(cols) - \stmt -> - val = try decode! stmt - Ok (mapper val) + \stmt -> + val = try(decode!, stmt) + Ok(mapper(val)), + ) RowCountErr err : [NoRowsReturned, TooManyRowsReturned]err # internal use only decode_exactly_one_row! : Stmt, SqlDecode a (RowCountErr err) => Result a (SqlDecodeErr (RowCountErr err)) -decode_exactly_one_row! = \stmt, @SqlDecode gen_decode -> - cols = columns! stmt - decode_row! = gen_decode cols +decode_exactly_one_row! = \stmt, @SqlDecode(gen_decode) -> + cols = columns!(stmt) + decode_row! = gen_decode(cols) - when try step! stmt is + when try(step!, stmt) is Row -> - row = try decode_row! stmt - when try step! stmt is + row = try(decode_row!, stmt) + when try(step!, stmt) is Done -> - Ok row + Ok(row) Row -> - Err TooManyRowsReturned + Err(TooManyRowsReturned) Done -> - Err NoRowsReturned + Err(NoRowsReturned) # internal use only decode_rows! : Stmt, SqlDecode a err => Result (List a) (SqlDecodeErr err) -decode_rows! = \stmt, @SqlDecode gen_decode -> - cols = columns! stmt - decode_row! = gen_decode cols +decode_rows! = \stmt, @SqlDecode(gen_decode) -> + cols = columns!(stmt) + decode_row! = gen_decode(cols) helper! = \out -> - when try step! stmt is + when try(step!, stmt) is Done -> - Ok out + Ok(out) Row -> - row = try decode_row! stmt + row = try(decode_row!, stmt) - List.append out row + List.append(out, row) |> helper! - helper! [] + helper!([]) # internal use only decoder : (Value -> Result a (SqlDecodeErr err)) -> (Str -> SqlDecode a err) -decoder = \fn -> \name -> - @SqlDecode \cols -> - - found = List.findFirstIndex cols \x -> x == name - when found is - Ok index -> - \stmt -> - try column_value! stmt index - |> fn - - Err NotFound -> - \_ -> - Err (FieldNotFound name) +decoder = \fn -> + \name -> + @SqlDecode( + \cols -> + + found = List.find_first_index(cols, \x -> x == name) + when found is + Ok(index) -> + \stmt -> + try(column_value!, stmt, index) + |> fn + + Err(NotFound) -> + \_ -> + Err(FieldNotFound(name)), + ) ## Decode a [Value] keeping it tagged. This is useful when data could be many possible types. ## @@ -440,18 +447,20 @@ decoder = \fn -> \name -> ## } ## ``` tagged_value : Str -> SqlDecode Value [] -tagged_value = decoder \val -> - Ok val +tagged_value = decoder( + \val -> + Ok(val), +) to_unexpected_type_err = \val -> type = when val is - Integer _ -> Integer - Real _ -> Real - String _ -> String - Bytes _ -> Bytes + Integer(_) -> Integer + Real(_) -> Real + String(_) -> String + Bytes(_) -> Bytes Null -> Null - Err (UnexpectedType type) + Err(UnexpectedType(type)) UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] @@ -470,33 +479,41 @@ UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] ## } ## ``` str : Str -> SqlDecode Str UnexpectedTypeErr -str = decoder \val -> - when val is - String s -> Ok s - _ -> to_unexpected_type_err val +str = decoder( + \val -> + when val is + String(s) -> Ok(s) + _ -> to_unexpected_type_err(val), +) ## Decode a [Value] to a [List U8]. bytes : Str -> SqlDecode (List U8) UnexpectedTypeErr -bytes = decoder \val -> - when val is - Bytes b -> Ok b - _ -> to_unexpected_type_err val +bytes = decoder( + \val -> + when val is + Bytes(b) -> Ok(b) + _ -> to_unexpected_type_err(val), +) # internal use only int_decoder : (I64 -> Result a err) -> (Str -> SqlDecode a [FailedToDecodeInteger err]UnexpectedTypeErr) int_decoder = \cast -> - decoder \val -> - when val is - Integer i -> cast i |> Result.mapErr FailedToDecodeInteger - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Integer(i) -> cast(i) |> Result.map_err(FailedToDecodeInteger) + _ -> to_unexpected_type_err(val), + ) # internal use only real_decoder : (F64 -> Result a err) -> (Str -> SqlDecode a [FailedToDecodeReal err]UnexpectedTypeErr) real_decoder = \cast -> - decoder \val -> - when val is - Real r -> cast r |> Result.mapErr FailedToDecodeReal - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Real(r) -> cast(r) |> Result.map_err(FailedToDecodeReal) + _ -> to_unexpected_type_err(val), + ) ## Decode a [Value] to a [I64]. ## @@ -513,43 +530,43 @@ real_decoder = \cast -> ## } ## ``` i64 : Str -> SqlDecode I64 [FailedToDecodeInteger []]UnexpectedTypeErr -i64 = int_decoder Ok +i64 = int_decoder(Ok) ## Decode a [Value] to a [I32]. i32 : Str -> SqlDecode I32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i32 = int_decoder Num.toI32Checked +i32 = int_decoder(Num.to_i32_checked) ## Decode a [Value] to a [I16]. i16 : Str -> SqlDecode I16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i16 = int_decoder Num.toI16Checked +i16 = int_decoder(Num.to_i16_checked) ## Decode a [Value] to a [I8]. i8 : Str -> SqlDecode I8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -i8 = int_decoder Num.toI8Checked +i8 = int_decoder(Num.to_i8_checked) ## Decode a [Value] to a [U64]. u64 : Str -> SqlDecode U64 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u64 = int_decoder Num.toU64Checked +u64 = int_decoder(Num.to_u64_checked) ## Decode a [Value] to a [U32]. u32 : Str -> SqlDecode U32 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u32 = int_decoder Num.toU32Checked +u32 = int_decoder(Num.to_u32_checked) ## Decode a [Value] to a [U16]. u16 : Str -> SqlDecode U16 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u16 = int_decoder Num.toU16Checked +u16 = int_decoder(Num.to_u16_checked) ## Decode a [Value] to a [U8]. u8 : Str -> SqlDecode U8 [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -u8 = int_decoder Num.toU8Checked +u8 = int_decoder(Num.to_u8_checked) ## Decode a [Value] to a [F64]. f64 : Str -> SqlDecode F64 [FailedToDecodeReal []]UnexpectedTypeErr -f64 = real_decoder Ok +f64 = real_decoder(Ok) ## Decode a [Value] to a [F32]. f32 : Str -> SqlDecode F32 [FailedToDecodeReal []]UnexpectedTypeErr -f32 = real_decoder (\x -> Num.toF32 x |> Ok) +f32 = real_decoder(\x -> Num.to_f32(x) |> Ok) # TODO: Mising Num.toDec and Num.toDecChecked # dec = realSqlDecoder Ok @@ -562,77 +579,85 @@ Nullable a : [NotNull a, Null] ## Decode a [Value] to a [Nullable Str]. nullable_str : Str -> SqlDecode (Nullable Str) UnexpectedTypeErr -nullable_str = decoder \val -> - when val is - String s -> Ok (NotNull s) - Null -> Ok Null - _ -> to_unexpected_type_err val +nullable_str = decoder( + \val -> + when val is + String(s) -> Ok(NotNull(s)) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), +) ## Decode a [Value] to a [Nullable (List U8)]. nullable_bytes : Str -> SqlDecode (Nullable (List U8)) UnexpectedTypeErr -nullable_bytes = decoder \val -> - when val is - Bytes b -> Ok (NotNull b) - Null -> Ok Null - _ -> to_unexpected_type_err val +nullable_bytes = decoder( + \val -> + when val is + Bytes(b) -> Ok(NotNull(b)) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), +) # internal use only nullable_int_decoder : (I64 -> Result a err) -> (Str -> SqlDecode (Nullable a) [FailedToDecodeInteger err]UnexpectedTypeErr) nullable_int_decoder = \cast -> - decoder \val -> - when val is - Integer i -> cast i |> Result.map NotNull |> Result.mapErr FailedToDecodeInteger - Null -> Ok Null - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Integer(i) -> cast(i) |> Result.map(NotNull) |> Result.map_err(FailedToDecodeInteger) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), + ) # internal use only nullable_real_decoder : (F64 -> Result a err) -> (Str -> SqlDecode (Nullable a) [FailedToDecodeReal err]UnexpectedTypeErr) nullable_real_decoder = \cast -> - decoder \val -> - when val is - Real r -> cast r |> Result.map NotNull |> Result.mapErr FailedToDecodeReal - Null -> Ok Null - _ -> to_unexpected_type_err val + decoder( + \val -> + when val is + Real(r) -> cast(r) |> Result.map(NotNull) |> Result.map_err(FailedToDecodeReal) + Null -> Ok(Null) + _ -> to_unexpected_type_err(val), + ) ## Decode a [Value] to a [Nullable I64]. nullable_i64 : Str -> SqlDecode (Nullable I64) [FailedToDecodeInteger []]UnexpectedTypeErr -nullable_i64 = nullable_int_decoder Ok +nullable_i64 = nullable_int_decoder(Ok) ## Decode a [Value] to a [Nullable I32]. nullable_i32 : Str -> SqlDecode (Nullable I32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i32 = nullable_int_decoder Num.toI32Checked +nullable_i32 = nullable_int_decoder(Num.to_i32_checked) ## Decode a [Value] to a [Nullable I16]. nullable_i16 : Str -> SqlDecode (Nullable I16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i16 = nullable_int_decoder Num.toI16Checked +nullable_i16 = nullable_int_decoder(Num.to_i16_checked) ## Decode a [Value] to a [Nullable I8]. nullable_i8 : Str -> SqlDecode (Nullable I8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_i8 = nullable_int_decoder Num.toI8Checked +nullable_i8 = nullable_int_decoder(Num.to_i8_checked) ## Decode a [Value] to a [Nullable U64]. nullable_u64 : Str -> SqlDecode (Nullable U64) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u64 = nullable_int_decoder Num.toU64Checked +nullable_u64 = nullable_int_decoder(Num.to_u64_checked) ## Decode a [Value] to a [Nullable U32]. nullable_u32 : Str -> SqlDecode (Nullable U32) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u32 = nullable_int_decoder Num.toU32Checked +nullable_u32 = nullable_int_decoder(Num.to_u32_checked) ## Decode a [Value] to a [Nullable U16]. nullable_u16 : Str -> SqlDecode (Nullable U16) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u16 = nullable_int_decoder Num.toU16Checked +nullable_u16 = nullable_int_decoder(Num.to_u16_checked) ## Decode a [Value] to a [Nullable U8]. nullable_u8 : Str -> SqlDecode (Nullable U8) [FailedToDecodeInteger [OutOfBounds]]UnexpectedTypeErr -nullable_u8 = nullable_int_decoder Num.toU8Checked +nullable_u8 = nullable_int_decoder(Num.to_u8_checked) ## Decode a [Value] to a [Nullable F64]. nullable_f64 : Str -> SqlDecode (Nullable F64) [FailedToDecodeReal []]UnexpectedTypeErr -nullable_f64 = nullable_real_decoder Ok +nullable_f64 = nullable_real_decoder(Ok) ## Decode a [Value] to a [Nullable F32]. nullable_f32 : Str -> SqlDecode (Nullable F32) [FailedToDecodeReal []]UnexpectedTypeErr -nullable_f32 = nullable_real_decoder (\x -> Num.toF32 x |> Ok) +nullable_f32 = nullable_real_decoder(\x -> Num.to_f32(x) |> Ok) # TODO: Mising Num.toDec and Num.toDecChecked # nullable_dec = nullable_real_decoder Ok @@ -640,7 +665,7 @@ nullable_f32 = nullable_real_decoder (\x -> Num.toF32 x |> Ok) # internal use only internal_to_external_error : InternalSqlite.SqliteError -> [SqliteErr ErrCode Str] internal_to_external_error = \{ code, message } -> - SqliteErr (code_from_i64 code) message + SqliteErr(code_from_i64(code), message) # internal use only code_from_i64 : I64 -> ErrCode @@ -706,7 +731,7 @@ code_from_i64 = \code -> else if code == 101 then Done else - Unknown code + Unknown(code) ## Convert a [ErrCode] to a pretty string for display purposes. errcode_to_str : ErrCode -> Str @@ -742,4 +767,4 @@ errcode_to_str = \code -> Warning -> "Warning: Warnings from sqlite3_log()" Row -> "Row: sqlite3_step() has another row ready" Done -> "Done: sqlite3_step() has finished executing" - Unknown c -> "Unknown: error code $(Num.toStr c) not known" + Unknown(c) -> "Unknown: error code $(Num.to_str(c)) not known" diff --git a/platform/Stderr.roc b/platform/Stderr.roc index d5aedf35..90bbc4ca 100644 --- a/platform/Stderr.roc +++ b/platform/Stderr.roc @@ -36,14 +36,14 @@ IOErr : [ handle_err : InternalIOErr.IOErrFromHost -> [StderrErr IOErr] handle_err = \{ tag, msg } -> when tag is - NotFound -> StderrErr NotFound - PermissionDenied -> StderrErr PermissionDenied - BrokenPipe -> StderrErr BrokenPipe - AlreadyExists -> StderrErr AlreadyExists - Interrupted -> StderrErr Interrupted - Unsupported -> StderrErr Unsupported - OutOfMemory -> StderrErr OutOfMemory - Other | EndOfFile -> StderrErr (Other msg) + NotFound -> StderrErr(NotFound) + PermissionDenied -> StderrErr(PermissionDenied) + BrokenPipe -> StderrErr(BrokenPipe) + AlreadyExists -> StderrErr(AlreadyExists) + Interrupted -> StderrErr(Interrupted) + Unsupported -> StderrErr(Unsupported) + OutOfMemory -> StderrErr(OutOfMemory) + Other | EndOfFile -> StderrErr(Other(msg)) ## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)), ## followed by a newline. @@ -51,8 +51,8 @@ handle_err = \{ tag, msg } -> ## > To write to `stderr` without the newline, see [Stderr.write!]. line! : Str => Result {} [StderrErr IOErr] line! = \str -> - Host.stderr_line! str - |> Result.mapErr handle_err + Host.stderr_line!(str) + |> Result.map_err(handle_err) ## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)). ## @@ -62,5 +62,5 @@ line! = \str -> ## > To write to `stderr` with a newline at the end, see [Stderr.line!]. write! : Str => Result {} [StderrErr IOErr] write! = \str -> - Host.stderr_write! str - |> Result.mapErr handle_err + Host.stderr_write!(str) + |> Result.map_err(handle_err) diff --git a/platform/Stdout.roc b/platform/Stdout.roc index 567ce3e2..4dcca59a 100644 --- a/platform/Stdout.roc +++ b/platform/Stdout.roc @@ -36,14 +36,14 @@ IOErr : [ handle_err : InternalIOErr.IOErrFromHost -> [StdoutErr IOErr] handle_err = \{ tag, msg } -> when tag is - NotFound -> StdoutErr NotFound - PermissionDenied -> StdoutErr PermissionDenied - BrokenPipe -> StdoutErr BrokenPipe - AlreadyExists -> StdoutErr AlreadyExists - Interrupted -> StdoutErr Interrupted - Unsupported -> StdoutErr Unsupported - OutOfMemory -> StdoutErr OutOfMemory - Other | EndOfFile -> StdoutErr (Other msg) + NotFound -> StdoutErr(NotFound) + PermissionDenied -> StdoutErr(PermissionDenied) + BrokenPipe -> StdoutErr(BrokenPipe) + AlreadyExists -> StdoutErr(AlreadyExists) + Interrupted -> StdoutErr(Interrupted) + Unsupported -> StdoutErr(Unsupported) + OutOfMemory -> StdoutErr(OutOfMemory) + Other | EndOfFile -> StdoutErr(Other(msg)) ## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)), ## followed by a newline. @@ -52,8 +52,8 @@ handle_err = \{ tag, msg } -> ## line! : Str => Result {} [StdoutErr IOErr] line! = \str -> - Host.stdout_line! str - |> Result.mapErr handle_err + Host.stdout_line!(str) + |> Result.map_err(handle_err) ## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)). ## @@ -63,5 +63,5 @@ line! = \str -> ## > To write to `stdout` with a newline at the end, see [Stdout.line!]. write! : Str => Result {} [StdoutErr IOErr] write! = \str -> - Host.stdout_write! str - |> Result.mapErr handle_err + Host.stdout_write!(str) + |> Result.map_err(handle_err) diff --git a/platform/Tcp.roc b/platform/Tcp.roc index 797f3d86..f451b8b2 100644 --- a/platform/Tcp.roc +++ b/platform/Tcp.roc @@ -42,7 +42,7 @@ parse_connect_err = \err -> "ErrorKind::Interrupted" -> Interrupted "ErrorKind::TimedOut" -> TimedOut "ErrorKind::Unsupported" -> Unsupported - other -> Unrecognized other + other -> Unrecognized(other) ## Represents errors that can occur when performing an effect with a [Stream]. StreamErr : [ @@ -66,7 +66,7 @@ parse_stream_err = \err -> "ErrorKind::Interrupted" -> Interrupted "ErrorKind::OutOfMemory" -> OutOfMemory "ErrorKind::BrokenPipe" -> BrokenPipe - other -> Unrecognized other + other -> Unrecognized(other) ## Opens a TCP connection to a remote host. ## @@ -85,9 +85,9 @@ parse_stream_err = \err -> ## connect! : Str, U16 => Result Stream ConnectErr connect! = \host, port -> - Host.tcp_connect! host port - |> Result.map @Stream - |> Result.mapErr parse_connect_err + Host.tcp_connect!(host, port) + |> Result.map(@Stream) + |> Result.map_err(parse_connect_err) ## Read up to a number of bytes from the TCP stream. ## @@ -99,9 +99,9 @@ connect! = \host, port -> ## ## > To read an exact number of bytes or fail, you can use [Tcp.read_exactly!] instead. read_up_to! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr] -read_up_to! = \@Stream stream, bytes_to_read -> - Host.tcp_read_up_to! stream bytes_to_read - |> Result.mapErr \err -> TcpReadErr (parse_stream_err err) +read_up_to! = \@Stream(stream), bytes_to_read -> + Host.tcp_read_up_to!(stream, bytes_to_read) + |> Result.map_err(\err -> TcpReadErr(parse_stream_err(err))) ## Read an exact number of bytes or fail. ## @@ -112,13 +112,15 @@ read_up_to! = \@Stream stream, bytes_to_read -> ## `TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. ## read_exactly! : Stream, U64 => Result (List U8) [TcpReadErr StreamErr, TcpUnexpectedEOF] -read_exactly! = \@Stream stream, bytes_to_read -> - Host.tcp_read_exactly! stream bytes_to_read - |> Result.mapErr \err -> - if err == unexpected_eof_error_message then - TcpUnexpectedEOF - else - TcpReadErr (parse_stream_err err) +read_exactly! = \@Stream(stream), bytes_to_read -> + Host.tcp_read_exactly!(stream, bytes_to_read) + |> Result.map_err( + \err -> + if err == unexpected_eof_error_message then + TcpUnexpectedEOF + else + TcpReadErr(parse_stream_err(err)), + ) ## Read until a delimiter or EOF is reached. ## @@ -132,9 +134,9 @@ read_exactly! = \@Stream stream, bytes_to_read -> ## > To read until a newline is found, you can use [Tcp.read_line!] which ## conveniently decodes to a [Str]. read_until! : Stream, U8 => Result (List U8) [TcpReadErr StreamErr] -read_until! = \@Stream stream, byte -> - Host.tcp_read_until! stream byte - |> Result.mapErr \err -> TcpReadErr (parse_stream_err err) +read_until! = \@Stream(stream), byte -> + Host.tcp_read_until!(stream, byte) + |> Result.map_err(\err -> TcpReadErr(parse_stream_err(err))) ## Read until a newline or EOF is reached. ## @@ -148,10 +150,10 @@ read_until! = \@Stream stream, byte -> ## read_line! : Stream => Result Str [TcpReadErr StreamErr, TcpReadBadUtf8 _] read_line! = \stream -> - bytes = read_until!? stream '\n' + bytes = read_until!?(stream, '\n') - Str.fromUtf8 bytes - |> Result.mapErr TcpReadBadUtf8 + Str.from_utf8(bytes) + |> Result.map_err(TcpReadBadUtf8) ## Writes bytes to a TCP stream. ## @@ -162,9 +164,9 @@ read_line! = \stream -> ## ## > To write a [Str], you can use [Tcp.write_utf8!] instead. write! : Stream, List U8 => Result {} [TcpWriteErr StreamErr] -write! = \@Stream stream, bytes -> - Host.tcp_write! stream bytes - |> Result.mapErr \err -> TcpWriteErr (parse_stream_err err) +write! = \@Stream(stream), bytes -> + Host.tcp_write!(stream, bytes) + |> Result.map_err(\err -> TcpWriteErr(parse_stream_err(err))) ## Writes a [Str] to a TCP stream, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8). ## @@ -176,7 +178,7 @@ write! = \@Stream stream, bytes -> ## > To write unformatted bytes, you can use [Tcp.write!] instead. write_utf8! : Stream, Str => Result {} [TcpWriteErr StreamErr] write_utf8! = \stream, str -> - write! stream (Str.toUtf8 str) + write!(stream, Str.to_utf8(str)) ## Convert a [ConnectErr] to a [Str] you can print. ## @@ -196,7 +198,7 @@ connect_err_to_str = \err -> Interrupted -> "Interrupted" TimedOut -> "TimedOut" Unsupported -> "Unsupported" - Unrecognized message -> "Unrecognized Error: $(message)" + Unrecognized(message) -> "Unrecognized Error: $(message)" ## Convert a [StreamErr] to a [Str] you can print. ## @@ -221,4 +223,4 @@ stream_err_to_str = \err -> Interrupted -> "Interrupted" OutOfMemory -> "OutOfMemory" BrokenPipe -> "BrokenPipe" - Unrecognized message -> "Unrecognized Error: $(message)" + Unrecognized(message) -> "Unrecognized Error: $(message)" diff --git a/platform/Url.roc b/platform/Url.roc index 71bc74a1..e9ca7c56 100644 --- a/platform/Url.roc +++ b/platform/Url.roc @@ -38,8 +38,8 @@ Url := Str implements [Inspect] ## on a [Str] first, and then pass that string to [Url.from_str]. This function will make use ## of the extra capacity. reserve : Url, U64 -> Url -reserve = \@Url str, cap -> - @Url (Str.reserve str (Num.intCast cap)) +reserve = \@Url(str), cap -> + @Url(Str.reserve(str, Num.int_cast(cap))) ## Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding) ## anything. @@ -63,7 +63,7 @@ reserve = \@Url str, cap -> ## Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors. ## from_str : Str -> Url -from_str = \str -> @Url str +from_str = \str -> @Url(str) ## Return a [Str] representation of this URL. ## ``` @@ -73,7 +73,7 @@ from_str = \str -> @Url str ## |> Url.to_str ## ``` to_str : Url -> Str -to_str = \@Url str -> str +to_str = \@Url(str) -> str ## [Percent-encodes](https://en.wikipedia.org/wiki/Percent-encoding) a ## [path component](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax) @@ -101,80 +101,80 @@ to_str = \@Url str -> str ## |> Url.append "" ## ``` append : Url, Str -> Url -append = \@Url url_str, suffix_unencoded -> - suffix = percent_encode suffix_unencoded +append = \@Url(url_str), suffix_unencoded -> + suffix = percent_encode(suffix_unencoded) - when Str.splitFirst url_str "?" is - Ok { before, after } -> + when Str.split_first(url_str, "?") is + Ok({ before, after }) -> bytes = - Str.countUtf8Bytes before + Str.count_utf8_bytes(before) + 1 # for "/" - + Str.countUtf8Bytes suffix + + Str.count_utf8_bytes(suffix) + 1 # for "?" - + Str.countUtf8Bytes after + + Str.count_utf8_bytes(after) before - |> Str.reserve bytes - |> append_help suffix - |> Str.concat "?" - |> Str.concat after + |> Str.reserve(bytes) + |> append_help(suffix) + |> Str.concat("?") + |> Str.concat(after) |> @Url - Err NotFound -> + Err(NotFound) -> # There wasn't a query, but there might still be a fragment - when Str.splitFirst url_str "#" is - Ok { before, after } -> + when Str.split_first(url_str, "#") is + Ok({ before, after }) -> bytes = - Str.countUtf8Bytes before + Str.count_utf8_bytes(before) + 1 # for "/" - + Str.countUtf8Bytes suffix + + Str.count_utf8_bytes(suffix) + 1 # for "#" - + Str.countUtf8Bytes after + + Str.count_utf8_bytes(after) before - |> Str.reserve bytes - |> append_help suffix - |> Str.concat "#" - |> Str.concat after + |> Str.reserve(bytes) + |> append_help(suffix) + |> Str.concat("#") + |> Str.concat(after) |> @Url - Err NotFound -> + Err(NotFound) -> # No query and no fragment, so just append it - @Url (append_help url_str suffix) + @Url(append_help(url_str, suffix)) ## Internal helper append_help : Str, Str -> Str append_help = \prefix, suffix -> - if Str.endsWith prefix "/" then - if Str.startsWith suffix "/" then + if Str.ends_with(prefix, "/") then + if Str.starts_with(suffix, "/") then # Avoid a double-slash by appending only the part of the suffix after the "/" - when Str.splitFirst suffix "/" is - Ok { after } -> + when Str.split_first(suffix, "/") is + Ok({ after }) -> # TODO `expect before == ""` - Str.concat prefix after + Str.concat(prefix, after) - Err NotFound -> + Err(NotFound) -> # This should never happen, because we already verified # that the suffix startsWith "/" # TODO `expect Bool.false` here with a comment - Str.concat prefix suffix + Str.concat(prefix, suffix) else # prefix ends with "/" but suffix doesn't start with one, so just append. - Str.concat prefix suffix - else if Str.startsWith suffix "/" then + Str.concat(prefix, suffix) + else if Str.starts_with(suffix, "/") then # Suffix starts with "/" but prefix doesn't end with one, so just append them. - Str.concat prefix suffix - else if Str.isEmpty prefix then + Str.concat(prefix, suffix) + else if Str.is_empty(prefix) then # Prefix is empty; return suffix. suffix - else if Str.isEmpty suffix then + else if Str.is_empty(suffix) then # Suffix is empty; return prefix. prefix else # Neither is empty, but neither has a "/", so add one in between. prefix - |> Str.concat "/" - |> Str.concat suffix + |> Str.concat("/") + |> Str.concat(suffix) ## Internal helper. This is intentionally unexposed so that you don't accidentally ## double-encode things. If you really want to percent-encode an arbitrary string, @@ -194,38 +194,42 @@ percent_encode : Str -> Str percent_encode = \input -> # Optimistically assume we won't need any percent encoding, and can have # the same capacity as the input string. If we're wrong, it will get doubled. - initial_output = List.withCapacity (Str.countUtf8Bytes input |> Num.intCast) + initial_output = List.with_capacity((Str.count_utf8_bytes(input) |> Num.int_cast)) answer = - List.walk (Str.toUtf8 input) initial_output \output, byte -> - # Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt - if - (byte >= 97 && byte <= 122) # lowercase ASCII - || (byte >= 65 && byte <= 90) # uppercase ASCII - || (byte >= 48 && byte <= 57) # digit - then - # This is the most common case: an unreserved character, - # which needs no encoding in a path - List.append output byte - else - when byte is - 46 # '.' - | 95 # '_' - | 126 # '~' - | 150 -> # '-' - # These special characters can all be unescaped in paths - List.append output byte - - _ -> - # This needs encoding in a path - suffix = - Str.toUtf8 percent_encoded - |> List.sublist { len: 3, start: 3 * Num.intCast byte } - - List.concat output suffix - - Str.fromUtf8 answer - |> Result.withDefault "" # This should never fail + List.walk( + Str.to_utf8(input), + initial_output, + \output, byte -> + # Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt + if + (byte >= 97 && byte <= 122) # lowercase ASCII + || (byte >= 65 && byte <= 90) # uppercase ASCII + || (byte >= 48 && byte <= 57) # digit + then + # This is the most common case: an unreserved character, + # which needs no encoding in a path + List.append(output, byte) + else + when byte is + 46 # '.' + | 95 # '_' + | 126 # '~' + | 150 -> # '-' + # These special characters can all be unescaped in paths + List.append(output, byte) + + _ -> + # This needs encoding in a path + suffix = + Str.to_utf8(percent_encoded) + |> List.sublist({ len: 3, start: 3 * Num.int_cast(byte) }) + + List.concat(output, suffix), + ) + + Str.from_utf8(answer) + |> Result.with_default("") # This should never fail ## Adds a [Str] query parameter to the end of the [Url]. ## @@ -247,35 +251,35 @@ percent_encode = \input -> ## ``` ## append_param : Url, Str, Str -> Url -append_param = \@Url url_str, key, value -> +append_param = \@Url(url_str), key, value -> { without_fragment, after_query } = - when Str.splitLast url_str "#" is - Ok { before, after } -> + when Str.split_last(url_str, "#") is + Ok({ before, after }) -> # The fragment is almost certainly going to be a small string, # so this interpolation should happen on the stack. { without_fragment: before, after_query: "#$(after)" } - Err NotFound -> + Err(NotFound) -> { without_fragment: url_str, after_query: "" } - encoded_key = percent_encode key - encoded_value = percent_encode value + encoded_key = percent_encode(key) + encoded_value = percent_encode(value) bytes = - Str.countUtf8Bytes without_fragment + Str.count_utf8_bytes(without_fragment) + 1 # for "?" or "&" - + Str.countUtf8Bytes encoded_key + + Str.count_utf8_bytes(encoded_key) + 1 # for "=" - + Str.countUtf8Bytes encoded_value - + Str.countUtf8Bytes after_query + + Str.count_utf8_bytes(encoded_value) + + Str.count_utf8_bytes(after_query) without_fragment - |> Str.reserve bytes - |> Str.concat (if has_query (@Url without_fragment) then "&" else "?") - |> Str.concat encoded_key - |> Str.concat "=" - |> Str.concat encoded_value - |> Str.concat after_query + |> Str.reserve(bytes) + |> Str.concat((if has_query(@Url(without_fragment)) then "&" else "?")) + |> Str.concat(encoded_key) + |> Str.concat("=") + |> Str.concat(encoded_value) + |> Str.concat(after_query) |> @Url ## Replaces the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part @@ -293,36 +297,36 @@ append_param = \@Url url_str, key, value -> ## |> Url.with_query "" ## ``` with_query : Url, Str -> Url -with_query = \@Url url_str, query_str -> +with_query = \@Url(url_str), query_str -> { without_fragment, after_query } = - when Str.splitLast url_str "#" is - Ok { before, after } -> + when Str.split_last(url_str, "#") is + Ok({ before, after }) -> # The fragment is almost certainly going to be a small string, # so this interpolation should happen on the stack. { without_fragment: before, after_query: "#$(after)" } - Err NotFound -> + Err(NotFound) -> { without_fragment: url_str, after_query: "" } before_query = - when Str.splitLast without_fragment "?" is - Ok { before } -> before - Err NotFound -> without_fragment + when Str.split_last(without_fragment, "?") is + Ok({ before }) -> before + Err(NotFound) -> without_fragment - if Str.isEmpty query_str then - @Url (Str.concat before_query after_query) + if Str.is_empty(query_str) then + @Url(Str.concat(before_query, after_query)) else bytes = - Str.countUtf8Bytes before_query + Str.count_utf8_bytes(before_query) + 1 # for "?" - + Str.countUtf8Bytes query_str - + Str.countUtf8Bytes after_query + + Str.count_utf8_bytes(query_str) + + Str.count_utf8_bytes(after_query) before_query - |> Str.reserve bytes - |> Str.concat "?" - |> Str.concat query_str - |> Str.concat after_query + |> Str.reserve(bytes) + |> Str.concat("?") + |> Str.concat(query_str) + |> Str.concat(after_query) |> @Url ## Returns the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part after @@ -341,15 +345,15 @@ with_query = \@Url url_str, query_str -> ## ``` ## query : Url -> Str -query = \@Url url_str -> +query = \@Url(url_str) -> without_fragment = - when Str.splitLast url_str "#" is - Ok { before } -> before - Err NotFound -> url_str + when Str.split_last(url_str, "#") is + Ok({ before }) -> before + Err(NotFound) -> url_str - when Str.splitLast without_fragment "?" is - Ok { after } -> after - Err NotFound -> "" + when Str.split_last(without_fragment, "?") is + Ok({ after }) -> after + Err(NotFound) -> "" ## Returns [Bool.true] if the URL has a `?` in it. ## @@ -364,8 +368,8 @@ query = \@Url url_str -> ## ``` ## has_query : Url -> Bool -has_query = \@Url url_str -> - Str.contains url_str "?" +has_query = \@Url(url_str) -> + Str.contains(url_str, "?") ## Returns the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part after ## the `#`, if it has one. @@ -383,10 +387,10 @@ has_query = \@Url url_str -> ## ``` ## fragment : Url -> Str -fragment = \@Url url_str -> - when Str.splitLast url_str "#" is - Ok { after } -> after - Err NotFound -> "" +fragment = \@Url(url_str) -> + when Str.split_last(url_str, "#") is + Ok({ after }) -> after + Err(NotFound) -> "" ## Replaces the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax). ## @@ -407,23 +411,23 @@ fragment = \@Url url_str -> ## ``` ## with_fragment : Url, Str -> Url -with_fragment = \@Url url_str, fragment_str -> - when Str.splitLast url_str "#" is - Ok { before } -> - if Str.isEmpty fragment_str then +with_fragment = \@Url(url_str), fragment_str -> + when Str.split_last(url_str, "#") is + Ok({ before }) -> + if Str.is_empty(fragment_str) then # If the given fragment is empty, remove the URL's fragment - @Url before + @Url(before) else # Replace the URL's old fragment with this one, discarding `after` - @Url "$(before)#$(fragment_str)" + @Url("$(before)#$(fragment_str)") - Err NotFound -> - if Str.isEmpty fragment_str then + Err(NotFound) -> + if Str.is_empty(fragment_str) then # If the given fragment is empty, leave the URL as having no fragment - @Url url_str + @Url(url_str) else # The URL didn't have a fragment, so give it this one - @Url "$(url_str)#$(fragment_str)" + @Url("$(url_str)#$(fragment_str)") ## Returns [Bool.true] if the URL has a `#` in it. ## @@ -438,8 +442,8 @@ with_fragment = \@Url url_str, fragment_str -> ## ``` ## has_fragment : Url -> Bool -has_fragment = \@Url url_str -> - Str.contains url_str "#" +has_fragment = \@Url(url_str) -> + Str.contains(url_str, "#") # Adapted from the percent-encoding crate, Β© The rust-url developers, Apache2-licensed # @@ -449,12 +453,15 @@ percent_encoded = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13% query_params : Url -> Dict Str Str query_params = \url -> - query url - |> Str.splitOn "&" - |> List.walk (Dict.empty {}) \dict, pair -> - when Str.splitFirst pair "=" is - Ok { before, after } -> Dict.insert dict before after - Err NotFound -> Dict.insert dict pair "" + query(url) + |> Str.split_on("&") + |> List.walk( + Dict.empty({}), + \dict, pair -> + when Str.split_first(pair, "=") is + Ok({ before, after }) -> Dict.insert(dict, before, after) + Err(NotFound) -> Dict.insert(dict, pair, ""), + ) ## Returns the URL's [path](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part after ## the scheme and authority (e.g. `https://`) but before any `?` or `#` it might have. @@ -473,23 +480,23 @@ query_params = \url -> ## |> Url.path ## ``` path : Url -> Str -path = \@Url urlStr -> - withoutAuthority = - when Str.splitFirst urlStr ":" is - Ok { after } -> - when Str.splitFirst after "//" is +path = \@Url(url_str) -> + without_authority = + when Str.split_first(url_str, ":") is + Ok({ after }) -> + when Str.split_first(after, "//") is # Only drop the `//` if it's right after the `://` like in `https://` # (so, `before` is empty) - otherwise, the `//` is part of the path! - Ok { before, after: afterSlashes } if Str.isEmpty before -> afterSlashes + Ok({ before, after: after_slashes }) if Str.is_empty(before) -> after_slashes _ -> after # There's no `//` and also no `:` so this must be a path-only URL, e.g. "/foo?bar=baz#blah" - Err NotFound -> urlStr + Err(NotFound) -> url_str # Drop the query and/or fragment - when Str.splitLast withoutAuthority "?" is - Ok { before } -> before - Err NotFound -> - when Str.splitLast withoutAuthority "#" is - Ok { before } -> before - Err NotFound -> withoutAuthority + when Str.split_last(without_authority, "?") is + Ok({ before }) -> before + Err(NotFound) -> + when Str.split_last(without_authority, "#") is + Ok({ before }) -> before + Err(NotFound) -> without_authority diff --git a/platform/Utc.roc b/platform/Utc.roc index 9415b405..7703d193 100644 --- a/platform/Utc.roc +++ b/platform/Utc.roc @@ -19,24 +19,24 @@ Utc := I128 implements [Inspect] ## Duration since UNIX EPOCH now! : {} => Utc now! = \{} -> - @Utc (Num.toI128 (Host.posix_time! {})) + @Utc(Num.to_i128(Host.posix_time!({}))) # Constant number of nanoseconds in a millisecond nanos_per_milli = 1_000_000 ## Convert Utc timestamp to milliseconds to_millis_since_epoch : Utc -> I128 -to_millis_since_epoch = \@Utc nanos -> +to_millis_since_epoch = \@Utc(nanos) -> nanos // nanos_per_milli ## Convert milliseconds to Utc timestamp from_millis_since_epoch : I128 -> Utc from_millis_since_epoch = \millis -> - @Utc (millis * nanos_per_milli) + @Utc((millis * nanos_per_milli)) ## Convert Utc timestamp to nanoseconds to_nanos_since_epoch : Utc -> I128 -to_nanos_since_epoch = \@Utc nanos -> +to_nanos_since_epoch = \@Utc(nanos) -> nanos ## Convert nanoseconds to Utc timestamp @@ -46,34 +46,34 @@ from_nanos_since_epoch = @Utc ## Calculate milliseconds between two Utc timestamps delta_as_millis : Utc, Utc -> U128 delta_as_millis = \utc_a, utc_b -> - (delta_as_nanos utc_a utc_b) // nanos_per_milli + (delta_as_nanos(utc_a, utc_b)) // nanos_per_milli ## Calculate nanoseconds between two Utc timestamps delta_as_nanos : Utc, Utc -> U128 -delta_as_nanos = \@Utc nanos_a, @Utc nanos_b -> +delta_as_nanos = \@Utc(nanos_a), @Utc(nanos_b) -> # bitwiseXor for best performance - nanos_a_shifted = Num.bitwiseXor (Num.toU128 nanos_a) (Num.shiftLeftBy 1 127) - nanos_b_shifted = Num.bitwiseXor (Num.toU128 nanos_b) (Num.shiftLeftBy 1 127) + nanos_a_shifted = Num.bitwise_xor(Num.to_u128(nanos_a), Num.shift_left_by(1, 127)) + nanos_b_shifted = Num.bitwise_xor(Num.to_u128(nanos_b), Num.shift_left_by(1, 127)) - Num.absDiff nanos_a_shifted nanos_b_shifted + Num.abs_diff(nanos_a_shifted, nanos_b_shifted) ## Convert Utc timestamp to ISO 8601 string ## Example: 2023-11-14T23:39:39Z to_iso_8601 : Utc -> Str -to_iso_8601 = \@Utc nanos -> +to_iso_8601 = \@Utc(nanos) -> nanos - |> Num.divTrunc nanos_per_milli + |> Num.div_trunc(nanos_per_milli) |> InternalDateTime.epoch_millis_to_datetime |> InternalDateTime.to_iso_8601 # TESTS -expect delta_as_nanos (from_nanos_since_epoch 0) (from_nanos_since_epoch 0) == 0 -expect delta_as_nanos (from_nanos_since_epoch 1) (from_nanos_since_epoch 2) == 1 -expect delta_as_nanos (from_nanos_since_epoch -1) (from_nanos_since_epoch 1) == 2 -expect delta_as_nanos (from_nanos_since_epoch Num.minI128) (from_nanos_since_epoch Num.maxI128) == Num.maxU128 +expect delta_as_nanos(from_nanos_since_epoch(0), from_nanos_since_epoch(0)) == 0 +expect delta_as_nanos(from_nanos_since_epoch(1), from_nanos_since_epoch(2)) == 1 +expect delta_as_nanos(from_nanos_since_epoch(-1), from_nanos_since_epoch(1)) == 2 +expect delta_as_nanos(from_nanos_since_epoch(Num.min_i128), from_nanos_since_epoch(Num.max_i128)) == Num.max_u128 -expect delta_as_millis (from_millis_since_epoch 0) (from_millis_since_epoch 0) == 0 -expect delta_as_millis (from_nanos_since_epoch 1) (from_nanos_since_epoch 2) == 0 -expect delta_as_millis (from_millis_since_epoch 1) (from_millis_since_epoch 2) == 1 -expect delta_as_millis (from_millis_since_epoch -1) (from_millis_since_epoch 1) == 2 -expect delta_as_millis (from_nanos_since_epoch Num.minI128) (from_nanos_since_epoch Num.maxI128) == Num.maxU128 // nanos_per_milli +expect delta_as_millis(from_millis_since_epoch(0), from_millis_since_epoch(0)) == 0 +expect delta_as_millis(from_nanos_since_epoch(1), from_nanos_since_epoch(2)) == 0 +expect delta_as_millis(from_millis_since_epoch(1), from_millis_since_epoch(2)) == 1 +expect delta_as_millis(from_millis_since_epoch(-1), from_millis_since_epoch(1)) == 2 +expect delta_as_millis(from_nanos_since_epoch(Num.min_i128), from_nanos_since_epoch(Num.max_i128)) == Num.max_u128 // nanos_per_milli diff --git a/platform/libapp.roc b/platform/libapp.roc index 6c423580..843c37b0 100644 --- a/platform/libapp.roc +++ b/platform/libapp.roc @@ -8,11 +8,14 @@ Model : {} # That file is an executable that runs a webserver that returns "I'm a stub...". init! : {} => Result Model [] -init! = \_ -> Ok {} +init! = \_ -> Ok({}) respond! : _, _ => Result _ [] -respond! = \_, _ -> Ok { - status: 200, - headers: [], - body: Str.toUtf8 "I'm a stub, I should be replaced by the user's Roc app.", - } +respond! = \_, _ -> + Ok( + { + status: 200, + headers: [], + body: Str.to_utf8("I'm a stub, I should be replaced by the user's Roc app."), + }, + ) diff --git a/platform/main.roc b/platform/main.roc index d6f513f4..9c35d146 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -29,33 +29,34 @@ import InternalHttp init_for_host! : I32 => Result (Box Model) I32 init_for_host! = \_ -> - when init! {} is - Ok model -> Ok (Box.box model) - Err (Exit code msg) -> - if Str.isEmpty msg then - Err code + when init!({}) is + Ok(model) -> Ok(Box.box(model)) + Err(Exit(code, msg)) -> + if Str.is_empty(msg) then + Err(code) else - _ = Stderr.line! msg - Err code + _ = Stderr.line!(msg) + Err(code) - Err err -> - _ = Stderr.line! + Err(err) -> + _ = Stderr.line!( """ Program exited with error: - $(Inspect.toStr err) + $(Inspect.to_str(err)) Tip: If you do not want to exit on this error, use `Result.mapErr` to handle the error. Docs for `Result.mapErr`: - """ - Err 1 + """, + ) + Err(1) respond_for_host! : InternalHttp.RequestToAndFromHost, Box Model => InternalHttp.ResponseToAndFromHost respond_for_host! = \request, boxed_model -> - when respond! (InternalHttp.from_host_request request) (Box.unbox boxed_model) is - Ok response -> InternalHttp.to_host_response response - Err (ServerErr msg) -> + when respond!(InternalHttp.from_host_request(request), Box.unbox(boxed_model)) is + Ok(response) -> InternalHttp.to_host_response(response) + Err(ServerErr(msg)) -> # dicard the err here if stderr fails - _ = Stderr.line! msg + _ = Stderr.line!(msg) # returns a http server error response { @@ -64,16 +65,17 @@ respond_for_host! = \request, boxed_model -> body: [], } - Err err -> + Err(err) -> # dicard the err here if stderr fails - _ = Stderr.line! + _ = Stderr.line!( """ Server error: - $(Inspect.toStr err) + $(Inspect.to_str(err)) Tip: If you do not want to see this error, use `Result.mapErr` to handle the error. Docs for `Result.mapErr`: - """ + """, + ) # returns a http server error response { From c5af2b416a3e3c9522e2ef6445d46c8ffec91915 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 9 Jan 2025 11:28:47 +1100 Subject: [PATCH 02/20] use release of roc-utils --- examples/file-upload-form.roc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/file-upload-form.roc b/examples/file-upload-form.roc index cd63343f..51255020 100644 --- a/examples/file-upload-form.roc +++ b/examples/file-upload-form.roc @@ -1,8 +1,6 @@ app [Model, init!, respond!] { pf: platform "../platform/main.roc", - - # TODO replace with roc-utils package release compatible with snake_case_builtins - utils: "../../roc-utils/package/main.roc", + utils: "https://github.com/quelgar/roc-utils/releases/download/v0.2.0/Ln38Q74rVeU6KAlT8dsB6nyKlUoD-O-43H41nCJ52Yk.tar.br", } import utils.Base64 From 468a457021f237921086feffe44126d9cf8ddbf3 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 9 Jan 2025 11:45:53 +1100 Subject: [PATCH 03/20] fix syntax in build.roc --- build.roc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.roc b/build.roc index 3fd3c487..74087bea 100644 --- a/build.roc +++ b/build.roc @@ -19,8 +19,10 @@ main! : _ => Result {} _ main! = \args -> parsed_args = - Cli.parse_or_display_message(cli_parser, args, Arg.to_os_raw) - |> try Result.on_err! \message -> Err (Exit 1 message) + Result.on_err!( + Cli.parse_or_display_message(cli_parser, args, Arg.to_os_raw), + \message -> Err (Exit 1 message), + )? run!(parsed_args) @@ -48,7 +50,7 @@ run! = \maybe_roc -> build_stub_app_lib!(roc_cmd, stub_lib_path)? - (cargo_build_host! {})? + cargo_build_host!({})? rust_target_folder = get_rust_target_folder!({})? From b9ebd5f777a3db3ffff99b54f8d37fc4d34e475b Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 9 Jan 2025 17:30:09 +1100 Subject: [PATCH 04/20] update flake --- flake.lock | 11 ++++++----- flake.nix | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 0990e2a4..d4192418 100644 --- a/flake.lock +++ b/flake.lock @@ -102,15 +102,16 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1736378239, - "narHash": "sha256-J7/bHTiRAefX/czxRkTbik/iMaJDL5ZmTnMfXJE+/nY=", - "owner": "roc-lang", + "lastModified": 1736384668, + "narHash": "sha256-3u+ifH9xhB0l+QCP2pFcDoi1OgD8+cq+/Wo1ZVOCC1c=", + "owner": "smores56", "repo": "roc", - "rev": "fbf448cac882ed9f81cb68242e0091a75f4f202d", + "rev": "82b7279485165c7d58a69f5aa0c3580d0826bf2d", "type": "github" }, "original": { - "owner": "roc-lang", + "owner": "smores56", + "ref": "remove-task", "repo": "roc", "type": "github" } diff --git a/flake.nix b/flake.nix index 70724497..04abe815 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "basic-webserver devShell flake"; inputs = { - roc.url = "github:roc-lang/roc"; + roc.url = "github:smores56/roc?ref=remove-task"; nixpkgs.follows = "roc/nixpkgs"; From 9fda27aa8069de2cb95653162200a30c1763d06d Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Thu, 9 Jan 2025 17:31:15 +1100 Subject: [PATCH 05/20] update doc comments to PNC --- platform/Cmd.roc | 22 +++--- platform/Env.roc | 2 +- platform/File.roc | 23 +++--- platform/Http.roc | 11 ++- platform/MultipartFormData.roc | 18 ++--- platform/Path.roc | 29 ++++---- platform/SplitList.roc | 2 +- platform/Sqlite.roc | 34 ++++----- platform/Tcp.roc | 29 ++++---- platform/Url.roc | 125 ++++++++++++--------------------- 10 files changed, 128 insertions(+), 167 deletions(-) diff --git a/platform/Cmd.roc b/platform/Cmd.roc index 20cdb128..c79e30cc 100644 --- a/platform/Cmd.roc +++ b/platform/Cmd.roc @@ -39,8 +39,8 @@ new = \program -> ## ## ``` ## # Represent the command "ls -l" -## Cmd.new "ls" -## |> Cmd.arg "-l" +## Cmd.new("ls") +## |> Cmd.arg("-l") ## ``` ## arg : Cmd, Str -> Cmd @@ -52,8 +52,8 @@ arg = \@Cmd(cmd), value -> ## ## ``` ## # Represent the command "ls -l -a" -## Cmd.new "ls" -## |> Cmd.args ["-l", "-a"] +## Cmd.new("ls") +## |> Cmd.args(["-l", "-a"]) ## ``` ## args : Cmd, List Str -> Cmd @@ -64,8 +64,8 @@ args = \@Cmd(cmd), values -> ## ## ``` ## # Run "env" and add the environment variable "FOO" with value "BAR" -## Cmd.new "env" -## |> Cmd.env "FOO" "BAR" +## Cmd.new("env") +## |> Cmd.env("FOO", "BAR") ## ``` ## env : Cmd, Str, Str -> Cmd @@ -76,8 +76,8 @@ env = \@Cmd(cmd), key, value -> ## ## ``` ## # Run "env" and add the variables "FOO" and "BAZ" -## Cmd.new "env" -## |> Cmd.envs [("FOO", "BAR"), ("BAZ", "DUCK")] +## Cmd.new("env") +## |> Cmd.envs([("FOO", "BAR"), ("BAZ", "DUCK")]) ## ``` ## envs : Cmd, List (Str, Str) -> Cmd @@ -90,9 +90,9 @@ envs = \@Cmd(cmd), key_values -> ## ## ``` ## # Represents "env" with only "FOO" environment variable set -## Cmd.new "env" +## Cmd.new("env") ## |> Cmd.clear_envs -## |> Cmd.env "FOO" "BAR" +## |> Cmd.env("FOO", "BAR") ## ``` ## clear_envs : Cmd -> Cmd @@ -121,7 +121,7 @@ status! = \@Cmd(cmd) -> ## ## ``` ## # Call echo to print "hello world" -## Cmd.exec! "echo" ["hello world"] +## Cmd.exec!("echo", ["hello world"]) ## ``` exec! : Str, List Str => Result {} [CmdStatusErr InternalIOErr.IOErr] exec! = \program, arguments -> diff --git a/platform/Env.roc b/platform/Env.roc index 00aeaa07..01d3a2be 100644 --- a/platform/Env.roc +++ b/platform/Env.roc @@ -66,7 +66,7 @@ var! = \name -> ## ``` ## # Reads "NUM_THINGS" and decodes into a U16 ## get_u16_var! : Str => Result U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]] -## get_u16_var! = \var -> Env.decode! var +## get_u16_var! = \var -> Env.decode!(var) ## ``` ## ## If `NUM_THINGS=123` then `getU16Var` succeeds with the value of `123u16`. diff --git a/platform/File.roc b/platform/File.roc index dc762938..51e56b4c 100644 --- a/platform/File.roc +++ b/platform/File.roc @@ -51,10 +51,11 @@ IOErr : InternalIOErr.IOErr ## ## ``` ## # Writes `{"some":"json stuff"}` to the file `output.json`: -## File.write! -## { some: "json stuff" } -## (Path.from_str "output.json") -## Json.toCompactUtf8 +## File.write!( +## { some: "json stuff" }, +## Path.from_str("output.json"), +## Json.toCompactUtf8, +## ) ## ``` ## ## This opens the file first and closes it after writing to it. @@ -71,7 +72,7 @@ write! = \val, path, fmt -> ## ## ``` ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. -## File.write_bytes! [1, 2, 3] (Path.from_str "myfile.dat") +## File.write_bytes!([1, 2, 3], Path.from_str("myfile.dat"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -87,7 +88,7 @@ write_bytes! = \bytes, path -> ## ## ``` ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. -## File.write_utf8! "Hello!" "myfile.txt" +## File.write_utf8!("Hello!", "myfile.txt")? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -109,7 +110,7 @@ write_utf8! = \str, path -> ## ## ``` ## # Deletes the file named `myfile.dat` -## File.delete! (Path.from_str "myfile.dat") [1, 2, 3] +## File.delete!(Path.from_str("myfile.dat"), [1, 2, 3])? ## ``` ## ## > This does not securely erase the file's contents from disk; instead, the operating @@ -127,12 +128,12 @@ delete! = \path -> ## ## ``` ## # Read all the bytes in `myfile.txt`. -## File.read_bytes! "myfile.txt" +## bytes = File.read_bytes!("myfile.txt")? ## ``` ## ## This opens the file first and closes it after reading its contents. ## -## > To read and decode data from a file, you can use `File.read` instead. +## > To read and decode data from a file into a [Str], you can use [File.read_utf8!] instead. ## > ## > [Path.read_bytes!] does the same thing, except it takes a [Path] instead of a [Str]. read_bytes! : Str => Result (List U8) [FileReadErr Path IOErr] @@ -143,10 +144,10 @@ read_bytes! = \path -> ## ## ``` ## # Reads UTF-8 encoded text into a Str from the file "myfile.txt" -## File.read_utf8! "myfile.txt" +## str = File.read_utf8!("myfile.txt")? ## ``` ## -## This opens the file first and closes it after writing to it. +## This opens the file first and closes it after reading its contents. ## The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8. ## ## > To read unformatted bytes from a file, you can use [File.read_bytes!] instead. diff --git a/platform/Http.roc b/platform/Http.roc index 0fec9c22..8930500c 100644 --- a/platform/Http.roc +++ b/platform/Http.roc @@ -56,12 +56,11 @@ header = \(name, value) -> { name, value } ## ``` ## # Prints out the HTML of the Roc-lang website. ## response = -## { Http.default_request & url: "https://www.roc-lang.org" } -## |> Http.send! +## Http.send!({ Http.default_request & url: "https://www.roc-lang.org" }) ## ## response.body -## |> Str.fromUtf8 -## |> Result.withDefault "Invalid UTF-8" +## |> Str.from_utf8 +## |> Result.with_default("Invalid UTF-8") ## |> Stdout.line ## ``` send! : Request => Response @@ -77,8 +76,8 @@ send! = \request -> ## ``` ## import json.Json ## -## # On the server side we send `Encode.toBytes {foo: "Hello Json!"} Json.utf8` -## { foo } = Http.get! "http://localhost:8000" Json.utf8 +## # On the server side we send `Encode.to_bytes {foo: "Hello Json!"} Json.utf8` +## { foo } = Http.get!("http://localhost:8000", Json.utf8)? ## ``` get! : Str, fmt => Result body [HttpDecodingFailed] where body implements Decoding, fmt implements DecoderFormatting get! = \uri, fmt -> diff --git a/platform/MultipartFormData.roc b/platform/MultipartFormData.roc index 72960e2f..ef15bcef 100644 --- a/platform/MultipartFormData.roc +++ b/platform/MultipartFormData.roc @@ -40,17 +40,17 @@ doubledash = ['-', '-'] ## ## Example call: ## ```roc -## parse_content_f { +## parse_content_f({ ## upper: Str.toUtf8 "Content-Disposition:", ## lower: Str.toUtf8 "content-disposition:", -## } +## }) ## -## input = Str.toUtf8 "\r\nContent-Disposition: form-data; name=\"sometext\"\r\nSome text here..." -## actual = parseContentDispositionF input -## expected = Ok { +## input = Str.toUtf8("\r\nContent-Disposition: form-data; name=\"sometext\"\r\nSome text here...") +## actual = parseContentDispositionF(input) +## expected = Ok({ ## value: Str.toUtf8 " form-data; name=\"sometext\"", ## rest: Str.toUtf8 "\r\nSome text here...", -## } +## }) ## ``` ## parse_content_f : { upper : List U8, lower : List U8 } -> (List U8 -> Result { value : List U8, rest : List U8 } _) @@ -335,10 +335,10 @@ expect ## ## ``` ## expect -## bytes = Str.toUtf8 "todo=foo&status=bar" -## parsed = parse_form_url_encoded bytes |> Result.withDefault (Dict.empty {}) +## bytes = Str.toUtf8("todo=foo&status=bar") +## parsed = parse_form_url_encoded(bytes) |> Result.withDefault(Dict.empty({})) ## -## Dict.toList parsed == [("todo", "foo"), ("status", "bar")] +## Dict.toList(parsed) == [("todo", "foo"), ("status", "bar")] ## ``` parse_form_url_encoded : List U8 -> Result (Dict Str Str) [BadUtf8] parse_form_url_encoded = \bytes -> diff --git a/platform/Path.roc b/platform/Path.roc index 709b6424..4f3bf215 100644 --- a/platform/Path.roc +++ b/platform/Path.roc @@ -57,10 +57,11 @@ IOErr : InternalIOErr.IOErr ## ## ``` ## # Writes `{"some":"json stuff"}` to the file `output.json`: -## Path.write! -## { some: "json stuff" } -## (Path.from_str "output.json") -## Json.toCompactUtf8 +## Path.write!( +## { some: "json stuff" }, +## Path.from_str("output.json"), +## Json.toCompactUtf8, +## )? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -78,7 +79,7 @@ write! = \val, path, fmt -> ## ## ``` ## # Writes the bytes 1, 2, 3 to the file `myfile.dat`. -## Path.write_bytes! [1, 2, 3] (Path.from_str "myfile.dat") +## Path.write_bytes!([1, 2, 3], Path.from_str("myfile.dat"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -95,7 +96,7 @@ write_bytes! = \bytes, path -> ## ## ``` ## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`. -## Path.write_utf8! "Hello!" (Path.from_str "myfile.txt") +## Path.write_utf8!("Hello!", Path.from_str("myfile.txt"))? ## ``` ## ## This opens the file first and closes it after writing to it. @@ -231,9 +232,9 @@ type! = \path -> ## ## ``` ## # Each of these gives "foo/bar/baz.txt" -## Path.from_str "foo/bar/baz" |> Path.with_extension "txt" -## Path.from_str "foo/bar/baz." |> Path.with_extension "txt" -## Path.from_str "foo/bar/baz.xz" |> Path.with_extension "txt" +## Path.from_str("foo/bar/baz") |> Path.with_extension("txt") +## Path.from_str("foo/bar/baz.") |> Path.with_extension("txt") +## Path.from_str("foo/bar/baz.xz") |> Path.with_extension("txt") ## ``` with_extension : Path, Str -> Path with_extension = \path, extension -> @@ -274,7 +275,7 @@ with_extension = \path, extension -> ## ## ``` ## # Deletes the file named `myfile.dat` -## Path.delete (Path.from_str "myfile.dat") [1, 2, 3] +## Path.delete(Path.from_str("myfile.dat"), [1, 2, 3])? ## ``` ## ## > This does not securely erase the file's contents from disk; instead, the operating @@ -293,10 +294,10 @@ delete! = \path -> ## ## ``` ## # Reads UTF-8 encoded text into a Str from the file "myfile.txt" -## Path.read_utf8 (Path.from_str "myfile.txt") +## contents_str = Path.read_utf8(Path.from_str("myfile.txt"))? ## ``` ## -## This opens the file first and closes it after writing to it. +## This opens the file first and closes it after reading its contents. ## The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8. ## ## > To read unformatted bytes from a file, you can use [Path.read_bytes!] instead. @@ -315,12 +316,12 @@ read_utf8! = \path -> ## ## ``` ## # Read all the bytes in `myfile.txt`. -## Path.read_bytes! (Path.from_str "myfile.txt") +## contents_bytes = Path.read_bytes!(Path.from_str("myfile.txt"))? ## ``` ## ## This opens the file first and closes it after reading its contents. ## -## > To read and decode data from a file, you can use `Path.read` instead. +## > To read and decode data from a file into a [Str], you can use [Path.read_utf8!] instead. ## > ## > [`File.read_bytes`](File#read_bytes!) does the same thing, except it takes a [Str] instead of a [Path]. read_bytes! : Path => Result (List U8) [FileReadErr Path IOErr] diff --git a/platform/SplitList.roc b/platform/SplitList.roc index 51d0aca2..ae3f76fb 100644 --- a/platform/SplitList.roc +++ b/platform/SplitList.roc @@ -7,7 +7,7 @@ module [ ## Example: ## ```roc ## input = [1,2,3,4,5,6,7,3,4,0,0] -## actual = splitOnList input [3,4] +## actual = splitOnList(input, [3,4]) ## expected = [[1,2], [5,6,7], [0, 0]] ## ``` ## diff --git a/platform/Sqlite.roc b/platform/Sqlite.roc index b596b144..b2b00eed 100644 --- a/platform/Sqlite.roc +++ b/platform/Sqlite.roc @@ -145,19 +145,19 @@ Stmt := Box {} ## preparing the query each time it is called. This is usually done in `init!` with the prepared `Stmt` stored in the model. ## ## ``` -## prepared_query = try Sqlite.prepare! { +## prepared_query = Sqlite.prepare!({ ## path : "path/to/database.db", ## query : "SELECT * FROM todos;", -## } +## })? ## -## Sqlite.query_many_prepared! { +## Sqlite.query_many_prepared!({ ## stmt: prepared_query, ## bindings: [], ## rows: { Sqlite.decode_record <- ## id: Sqlite.i64 "id", ## task: Sqlite.str "task", ## }, -## } +## }) ## ``` prepare! : { @@ -203,14 +203,14 @@ reset! = \@Stmt(stmt) -> ## ## Example: ## ``` -## Sqlite.execute! { +## Sqlite.execute!({ ## path: "path/to/database.db", ## query: "INSERT INTO users (first, last) VALUES (:first, :last);", ## bindings: [ ## { name: ":first", value: String "John" }, ## { name: ":last", value: String "Smith" }, ## ], -## } +## })? ## ``` execute! : { @@ -252,12 +252,12 @@ execute_prepared! = \{ stmt, bindings } -> ## Example: ## ``` ## # count the number of rows in the `users` table -## count = try Sqlite.query! { +## count = Sqlite.query!({ ## path: db_path, ## query: "SELECT COUNT(*) as \"count\" FROM users;", ## bindings: [], ## row: Sqlite.u64 "count", -## } +## })? ## ``` query! : { @@ -292,7 +292,7 @@ query_prepared! = \{ stmt, bindings, row: decode } -> ## ## Example: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT * FROM todos;", ## bindings: [], @@ -300,7 +300,7 @@ query_prepared! = \{ stmt, bindings, row: decode } -> ## id: Sqlite.i64 "id", ## task: Sqlite.str "task", ## }, -## } +## })? ## ``` query_many! : { @@ -360,7 +360,7 @@ decode_record = \@SqlDecode(gen_first), @SqlDecode(gen_second), mapper -> ## ## Example: ## ``` -## Sqlite.i64 "id" |> Sqlite.map_value Num.toStr +## Sqlite.i64("id") |> Sqlite.map_value(Num.to_str) ## ``` map_value : SqlDecode a err, (a -> b) -> SqlDecode b err map_value = \@SqlDecode(gen_decode), mapper -> @@ -436,7 +436,7 @@ decoder = \fn -> ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `mixed_data` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, mix_data FROM users;", ## bindings: [], @@ -444,7 +444,7 @@ decoder = \fn -> ## id: Sqlite.i64 "id", ## mix_data: Sqlite.tagged_value "mixed_data", ## }, -## } +## })? ## ``` tagged_value : Str -> SqlDecode Value [] tagged_value = decoder( @@ -468,7 +468,7 @@ UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, name FROM users;", ## bindings: [], @@ -476,7 +476,7 @@ UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] ## id: Sqlite.i64 "id", ## task: Sqlite.str "name", ## }, -## } +## })? ## ``` str : Str -> SqlDecode Str UnexpectedTypeErr str = decoder( @@ -519,7 +519,7 @@ real_decoder = \cast -> ## ## For example here we build a decoder that decodes the rows into a list of records with `id` and `name` fields: ## ``` -## Sqlite.query_many! { +## rows = Sqlite.query_many!({ ## path: "path/to/database.db", ## query: "SELECT id, name FROM users;", ## bindings: [], @@ -527,7 +527,7 @@ real_decoder = \cast -> ## id: Sqlite.i64 "id", ## task: Sqlite.str "name", ## }, -## } +## })? ## ``` i64 : Str -> SqlDecode I64 [FailedToDecodeInteger []]UnexpectedTypeErr i64 = int_decoder(Ok) diff --git a/platform/Tcp.roc b/platform/Tcp.roc index f451b8b2..4171bd12 100644 --- a/platform/Tcp.roc +++ b/platform/Tcp.roc @@ -72,7 +72,7 @@ parse_stream_err = \err -> ## ## ``` ## # Connect to localhost:8080 -## stream = Tcp.connect! "localhost" 8080 +## stream = Tcp.connect!("localhost", 8080)? ## ``` ## ## The connection is automatically closed when the last reference to the stream is dropped. @@ -92,9 +92,8 @@ connect! = \host, port -> ## Read up to a number of bytes from the TCP stream. ## ## ``` -## # Read up to 64 bytes from the stream and convert to a Str -## received = File.read_up_to! stream 64 -## Str.fromUtf8 received +## # Read up to 64 bytes from the stream +## received_bytes = File.read_up_to!(stream, 64)? ## ``` ## ## > To read an exact number of bytes or fail, you can use [Tcp.read_exactly!] instead. @@ -106,7 +105,7 @@ read_up_to! = \@Stream(stream), bytes_to_read -> ## Read an exact number of bytes or fail. ## ## ``` -## bytes = File.read_exactly!? stream 64 +## bytes = File.read_exactly!(stream, 64)? ## ``` ## ## `TcpUnexpectedEOF` is returned if the stream ends before the specfied number of bytes is reached. @@ -126,7 +125,7 @@ read_exactly! = \@Stream(stream), bytes_to_read -> ## ## ``` ## # Read until null terminator -## File.read_until! stream 0 +## bytes = File.read_until!(stream, 0)? ## ``` ## ## If found, the delimiter is included as the last byte. @@ -142,15 +141,15 @@ read_until! = \@Stream(stream), byte -> ## ## ``` ## # Read a line and then print it to `stdout` -## lineStr = File.read_line! stream -## Stdout.line lineStr +## line_str = File.read_line!(stream)? +## Stdout.line(lineStr)? ## ``` ## ## If found, the newline is included as the last character in the [Str]. ## read_line! : Stream => Result Str [TcpReadErr StreamErr, TcpReadBadUtf8 _] read_line! = \stream -> - bytes = read_until!?(stream, '\n') + bytes = read_until!(stream, '\n')? Str.from_utf8(bytes) |> Result.map_err(TcpReadBadUtf8) @@ -159,7 +158,7 @@ read_line! = \stream -> ## ## ``` ## # Writes the bytes 1, 2, 3 -## Tcp.write!? stream [1, 2, 3] +## Tcp.write!(stream, [1, 2, 3])? ## ``` ## ## > To write a [Str], you can use [Tcp.write_utf8!] instead. @@ -172,7 +171,7 @@ write! = \@Stream(stream), bytes -> ## ## ``` ## # Write "Hi from Roc!" encoded as UTF-8 -## Tcp.write_utf8! stream "Hi from Roc!" +## Tcp.write_utf8!(stream, "Hi from Roc!")? ## ``` ## ## > To write unformatted bytes, you can use [Tcp.write!] instead. @@ -205,12 +204,12 @@ connect_err_to_str = \err -> ## ``` ## when err is ## TcpPerformErr (TcpReadErr err) -> -## errStr = Tcp.stream_err_to_str err -## Stderr.line "Error while reading: $(errStr)" +## errStr = Tcp.stream_err_to_str(err) +## Stderr.line("Error while reading: $(errStr)") ## ## TcpPerformErr (TcpWriteErr err) -> -## errStr = Tcp.stream_err_to_str err -## Stderr.line "Error while writing: $(errStr)" +## errStr = Tcp.stream_err_to_str(err) +## Stderr.line("Error while writing: $(errStr)") ## ``` ## stream_err_to_str : StreamErr -> Str diff --git a/platform/Url.roc b/platform/Url.roc index e9ca7c56..66fb817c 100644 --- a/platform/Url.roc +++ b/platform/Url.roc @@ -12,7 +12,6 @@ module [ with_query, with_fragment, query_params, - path, ] ## A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL). @@ -26,13 +25,13 @@ Url := Str implements [Inspect] ## ## The following example reserves 50 bytes, then builds the url `https://example.com/stuff?caf%C3%A9=du%20Monde&email=hi%40example.com`; ## ``` -## Url.from_str "https://example.com" -## |> Url.reserve 50 -## |> Url.append "stuff" -## |> Url.append_param "cafΓ©" "du Monde" -## |> Url.append_param "email" "hi@example.com" +## Url.from_str("https://example.com") +## |> Url.reserve(50) +## |> Url.append("stuff") +## |> Url.append_param("cafΓ©", "du Monde") +## |> Url.append_param("email", "hi@example.com") ## ``` -## The [Str.countUtf8Bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. +## The [Str.count_utf8_bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. ## ## There is no `Url.withCapacity` because it's better to reserve extra capacity ## on a [Str] first, and then pass that string to [Url.from_str]. This function will make use @@ -45,19 +44,19 @@ reserve = \@Url(str), cap -> ## anything. ## ## ``` -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## ``` ## ## URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`. ## ## ``` -## Url.from_str "/this/is#relative" +## Url.from_str("/this/is#relative") ## ``` ## ## Since nothing is validated, this can return invalid URLs. ## ## ``` -## Url.from_str "https://this is not a valid URL, not at all!" +## Url.from_str("https://this is not a valid URL, not at all!") ## ``` ## ## Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors. @@ -68,8 +67,8 @@ from_str = \str -> @Url(str) ## Return a [Str] representation of this URL. ## ``` ## # Gives "https://example.com/two%20words" -## Url.from_str "https://example.com" -## |> Url.append "two words" +## Url.from_str("https://example.com") +## |> Url.append("two words") ## |> Url.to_str ## ``` to_str : Url -> Str @@ -84,21 +83,21 @@ to_str = \@Url(str) -> str ## ## ``` ## # Gives https://example.com/some%20stuff -## Url.from_str "https://example.com" -## |> Url.append "some stuff" +## Url.from_str("https://example.com") +## |> Url.append("some stuff") ## ## # Gives https://example.com/stuff?search=blah#fragment -## Url.from_str "https://example.com?search=blah#fragment" -## |> Url.append "stuff" +## Url.from_str("https://example.com?search=blah#fragment") +## |> Url.append("stuff") ## ## # Gives https://example.com/things/stuff/more/etc/" ## Url.from_str "https://example.com/things/" -## |> Url.append "/stuff/" -## |> Url.append "/more/etc/" +## |> Url.append("/stuff/") +## |> Url.append("/more/etc/") ## ## # Gives https://example.com/things -## Url.from_str "https://example.com/things" -## |> Url.append "" +## Url.from_str("https://example.com/things") +## |> Url.append("") ## ``` append : Url, Str -> Url append = \@Url(url_str), suffix_unencoded -> @@ -181,8 +180,8 @@ append_help = \prefix, suffix -> ## you can always do: ## ## ``` -## Url.from_str "" -## |> Url.append myStrToEncode +## Url.from_str("") +## |> Url.append(myStrToEncode) ## |> Url.to_str ## ``` ## @@ -237,17 +236,17 @@ percent_encode = \input -> ## ## ``` ## # Gives https://example.com?email=someone%40example.com -## Url.from_str "https://example.com" -## |> Url.append_param "email" "someone@example.com" +## Url.from_str("https://example.com") +## |> Url.append_param("email", "someone@example.com") ## ``` ## ## This can be called multiple times on the same URL. ## ## ``` ## # Gives https://example.com?caf%C3%A9=du%20Monde&email=hi%40example.com -## Url.from_str "https://example.com" -## |> Url.append_param "cafΓ©" "du Monde" -## |> Url.append_param "email" "hi@example.com" +## Url.from_str("https://example.com") +## |> Url.append_param("cafΓ©", "du Monde") +## |> Url.append_param("email", "hi@example.com") ## ``` ## append_param : Url, Str, Str -> Url @@ -289,12 +288,12 @@ append_param = \@Url(url_str), key, value -> ## ## ``` ## # Gives https://example.com?newQuery=thisRightHere#stuff -## Url.from_str "https://example.com?key1=val1&key2=val2#stuff" -## |> Url.with_query "newQuery=thisRightHere" +## Url.from_str("https://example.com?key1=val1&key2=val2#stuff") +## |> Url.with_query("newQuery=thisRightHere") ## ## # Gives https://example.com#stuff -## Url.from_str "https://example.com?key1=val1&key2=val2#stuff" -## |> Url.with_query "" +## Url.from_str("https://example.com?key1=val1&key2=val2#stuff") +## |> Url.with_query("") ## ``` with_query : Url, Str -> Url with_query = \@Url(url_str), query_str -> @@ -336,11 +335,11 @@ with_query = \@Url(url_str), query_str -> ## ## ``` ## # Gives "key1=val1&key2=val2&key3=val3" -## Url.from_str "https://example.com?key1=val1&key2=val2&key3=val3#stuff" +## Url.from_str("https://example.com?key1=val1&key2=val2&key3=val3#stuff") ## |> Url.query ## ## # Gives "" -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.query ## ``` ## @@ -359,11 +358,11 @@ query = \@Url(url_str) -> ## ## ``` ## # Gives Bool.true -## Url.from_str "https://example.com?key=value#stuff" +## Url.from_str("https://example.com?key=value#stuff") ## |> Url.has_query ## ## # Gives Bool.false -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.has_query ## ``` ## @@ -378,11 +377,11 @@ has_query = \@Url(url_str) -> ## ## ``` ## # Gives "stuff" -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.fragment ## ## # Gives "" -## Url.from_str "https://example.com" +## Url.from_str("https://example.com") ## |> Url.fragment ## ``` ## @@ -398,15 +397,15 @@ fragment = \@Url(url_str) -> ## ## ``` ## # Gives https://example.com#things -## Url.from_str "https://example.com#stuff" -## |> Url.with_fragment "things" +## Url.from_str("https://example.com#stuff") +## |> Url.with_fragment("things") ## ## # Gives https://example.com#things -## Url.from_str "https://example.com" -## |> Url.with_fragment "things" +## Url.from_str("https://example.com") +## |> Url.with_fragment("things") ## ## # Gives https://example.com -## Url.from_str "https://example.com#stuff" +## Url.from_str("https://example.com#stuff") ## |> Url.with_fragment "" ## ``` ## @@ -433,11 +432,11 @@ with_fragment = \@Url(url_str), fragment_str -> ## ## ``` ## # Gives Bool.true -## Url.from_str "https://example.com?key=value#stuff" +## Url.from_str("https://example.com?key=value#stuff") ## |> Url.has_fragment ## ## # Gives Bool.false -## Url.from_str "https://example.com?key=value" +## Url.from_str("https://example.com?key=value") ## |> Url.has_fragment ## ``` ## @@ -462,41 +461,3 @@ query_params = \url -> Ok({ before, after }) -> Dict.insert(dict, before, after) Err(NotFound) -> Dict.insert(dict, pair, ""), ) - -## Returns the URL's [path](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part after -## the scheme and authority (e.g. `https://`) but before any `?` or `#` it might have. -## -## Returns `""` if the URL has no path. -## -## ``` -## # Gives "example.com/" -## Url.fromStr "https://example.com/?key1=val1&key2=val2&key3=val3#stuff" -## |> Url.path -## ``` -## -## ``` -## # Gives "/foo/" -## Url.fromStr "/foo/?key1=val1&key2=val2&key3=val3#stuff" -## |> Url.path -## ``` -path : Url -> Str -path = \@Url(url_str) -> - without_authority = - when Str.split_first(url_str, ":") is - Ok({ after }) -> - when Str.split_first(after, "//") is - # Only drop the `//` if it's right after the `://` like in `https://` - # (so, `before` is empty) - otherwise, the `//` is part of the path! - Ok({ before, after: after_slashes }) if Str.is_empty(before) -> after_slashes - _ -> after - - # There's no `//` and also no `:` so this must be a path-only URL, e.g. "/foo?bar=baz#blah" - Err(NotFound) -> url_str - - # Drop the query and/or fragment - when Str.split_last(without_authority, "?") is - Ok({ before }) -> before - Err(NotFound) -> - when Str.split_last(without_authority, "#") is - Ok({ before }) -> before - Err(NotFound) -> without_authority From d04f1a964b5992ddb7a608833383ca06e5b58bc0 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:11:52 +1100 Subject: [PATCH 06/20] update flake, restore back to roc repo --- flake.lock | 11 +++++------ flake.nix | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index d4192418..4fa25636 100644 --- a/flake.lock +++ b/flake.lock @@ -102,16 +102,15 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1736384668, - "narHash": "sha256-3u+ifH9xhB0l+QCP2pFcDoi1OgD8+cq+/Wo1ZVOCC1c=", - "owner": "smores56", + "lastModified": 1736460403, + "narHash": "sha256-Q+CVTjdbBCBLyoRQJ1K5VIun+03mlYo2LuplyYeWw4Q=", + "owner": "roc-lang", "repo": "roc", - "rev": "82b7279485165c7d58a69f5aa0c3580d0826bf2d", + "rev": "a69a326161a49c19186b8bf07b50c625d3b95686", "type": "github" }, "original": { - "owner": "smores56", - "ref": "remove-task", + "owner": "roc-lang", "repo": "roc", "type": "github" } diff --git a/flake.nix b/flake.nix index 04abe815..70724497 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "basic-webserver devShell flake"; inputs = { - roc.url = "github:smores56/roc?ref=remove-task"; + roc.url = "github:roc-lang/roc"; nixpkgs.follows = "roc/nixpkgs"; From 62b8696e7fcc7a433ff1004af87785254ad816c7 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:13:08 +1100 Subject: [PATCH 07/20] fix Json.to_compact_utf8 case --- platform/File.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/File.roc b/platform/File.roc index 51e56b4c..d4d42f1d 100644 --- a/platform/File.roc +++ b/platform/File.roc @@ -44,7 +44,7 @@ IOErr : InternalIOErr.IOErr ## ## First encode a `val` using a given `fmt` which implements the ability [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). ## -## For example, suppose you have a `Json.toCompactUtf8` which implements +## For example, suppose you have a `Json.to_compact_utf8` which implements ## [Encode.EncoderFormatting](https://www.roc-lang.org/builtins/Encode#EncoderFormatting). ## You can use this to write [JSON](https://en.wikipedia.org/wiki/JSON) ## data to a file like this: @@ -54,7 +54,7 @@ IOErr : InternalIOErr.IOErr ## File.write!( ## { some: "json stuff" }, ## Path.from_str("output.json"), -## Json.toCompactUtf8, +## Json.to_compact_utf8, ## ) ## ``` ## From 5af423c76f6bb8444ff335de0a6276cd2e63984e Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:14:29 +1100 Subject: [PATCH 08/20] fix String() PNC --- platform/Sqlite.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/Sqlite.roc b/platform/Sqlite.roc index b2b00eed..c8e09a44 100644 --- a/platform/Sqlite.roc +++ b/platform/Sqlite.roc @@ -207,8 +207,8 @@ reset! = \@Stmt(stmt) -> ## path: "path/to/database.db", ## query: "INSERT INTO users (first, last) VALUES (:first, :last);", ## bindings: [ -## { name: ":first", value: String "John" }, -## { name: ":last", value: String "Smith" }, +## { name: ":first", value: String("John") }, +## { name: ":last", value: String("Smith) }, ## ], ## })? ## ``` From 09f91d7ba0915ca78da91bea4eb20d0a02abc74e Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:17:39 +1100 Subject: [PATCH 09/20] fix PNC doc comments in Sqlite.roc --- platform/Sqlite.roc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/platform/Sqlite.roc b/platform/Sqlite.roc index c8e09a44..549722ef 100644 --- a/platform/Sqlite.roc +++ b/platform/Sqlite.roc @@ -154,8 +154,8 @@ Stmt := Box {} ## stmt: prepared_query, ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## }, ## }) ## ``` @@ -208,7 +208,7 @@ reset! = \@Stmt(stmt) -> ## query: "INSERT INTO users (first, last) VALUES (:first, :last);", ## bindings: [ ## { name: ":first", value: String("John") }, -## { name: ":last", value: String("Smith) }, +## { name: ":last", value: String("Smith") }, ## ], ## })? ## ``` @@ -256,7 +256,7 @@ execute_prepared! = \{ stmt, bindings } -> ## path: db_path, ## query: "SELECT COUNT(*) as \"count\" FROM users;", ## bindings: [], -## row: Sqlite.u64 "count", +## row: Sqlite.u64("count"), ## })? ## ``` query! : @@ -297,8 +297,8 @@ query_prepared! = \{ stmt, bindings, row: decode } -> ## query: "SELECT * FROM todos;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## }, ## })? ## ``` @@ -339,8 +339,8 @@ SqlDecode a err := List Str -> (Stmt => Result a (SqlDecodeErr err)) ## Example: ## ``` ## { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "task", +## id: Sqlite.i64("id"), +## task: Sqlite.str("task"), ## } ## ``` decode_record : SqlDecode a err, SqlDecode b err, (a, b -> c) -> SqlDecode c err @@ -441,8 +441,8 @@ decoder = \fn -> ## query: "SELECT id, mix_data FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## mix_data: Sqlite.tagged_value "mixed_data", +## id: Sqlite.i64("id"), +## mix_data: Sqlite.tagged_value("mixed_data"), ## }, ## })? ## ``` @@ -473,8 +473,8 @@ UnexpectedTypeErr : [UnexpectedType [Integer, Real, String, Bytes, Null]] ## query: "SELECT id, name FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "name", +## id: Sqlite.i64("id"), +## task: Sqlite.str("name"), ## }, ## })? ## ``` @@ -524,8 +524,8 @@ real_decoder = \cast -> ## query: "SELECT id, name FROM users;", ## bindings: [], ## rows: { Sqlite.decode_record <- -## id: Sqlite.i64 "id", -## task: Sqlite.str "name", +## id: Sqlite.i64("id"), +## task: Sqlite.str("name"), ## }, ## })? ## ``` From 77d5ee08ab8451606ad4e66803821d35acc8697c Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:19:56 +1100 Subject: [PATCH 10/20] fix PNC in doc comments for Tcp.roc --- platform/Tcp.roc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/Tcp.roc b/platform/Tcp.roc index 4171bd12..01b23fa4 100644 --- a/platform/Tcp.roc +++ b/platform/Tcp.roc @@ -183,8 +183,8 @@ write_utf8! = \stream, str -> ## ## ``` ## when err is -## TcpPerfomErr (TcpConnectErr connectErr) -> -## Stderr.line (Tcp.connect_err_to_str connectErr) +## TcpPerfomErr(TcpConnectErr(connectErr)) -> +## Stderr.line!(Tcp.connect_err_to_str(connectErr)) ## ``` ## connect_err_to_str : ConnectErr -> Str @@ -203,13 +203,13 @@ connect_err_to_str = \err -> ## ## ``` ## when err is -## TcpPerformErr (TcpReadErr err) -> +## TcpPerformErr(TcpReadErr(err)) -> ## errStr = Tcp.stream_err_to_str(err) -## Stderr.line("Error while reading: $(errStr)") +## Stderr.line!("Error while reading: $(errStr)") ## -## TcpPerformErr (TcpWriteErr err) -> +## TcpPerformErr(TcpWriteErr(err)) -> ## errStr = Tcp.stream_err_to_str(err) -## Stderr.line("Error while writing: $(errStr)") +## Stderr.line!("Error while writing: $(errStr)") ## ``` ## stream_err_to_str : StreamErr -> Str From 53fdf62bf111c6d2d3aa38493a9039867dd5cbcc Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:21:34 +1100 Subject: [PATCH 11/20] fix snake_case in Url.roc --- platform/Url.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/Url.roc b/platform/Url.roc index 66fb817c..0ae32651 100644 --- a/platform/Url.roc +++ b/platform/Url.roc @@ -33,7 +33,7 @@ Url := Str implements [Inspect] ## ``` ## The [Str.count_utf8_bytes](https://www.roc-lang.org/builtins/Str#countUtf8Bytes) function can be helpful in finding out how many bytes to reserve. ## -## There is no `Url.withCapacity` because it's better to reserve extra capacity +## There is no `Url.with_capacity` because it's better to reserve extra capacity ## on a [Str] first, and then pass that string to [Url.from_str]. This function will make use ## of the extra capacity. reserve : Url, U64 -> Url From 341acb033432022109fcae8bb1f772bd572e75eb Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Fri, 10 Jan 2025 09:25:50 +1100 Subject: [PATCH 12/20] add Url.path back --- platform/Url.roc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/platform/Url.roc b/platform/Url.roc index 0ae32651..b96cd41b 100644 --- a/platform/Url.roc +++ b/platform/Url.roc @@ -12,6 +12,7 @@ module [ with_query, with_fragment, query_params, + path, ] ## A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL). @@ -461,3 +462,41 @@ query_params = \url -> Ok({ before, after }) -> Dict.insert(dict, before, after) Err(NotFound) -> Dict.insert(dict, pair, ""), ) + +## Returns the URL's [path](https://en.wikipedia.org/wiki/URL#Syntax)β€”the part after +## the scheme and authority (e.g. `https://`) but before any `?` or `#` it might have. +## +## Returns `""` if the URL has no path. +## +## ``` +## # Gives "example.com/" +## Url.fromStr("https://example.com/?key1=val1&key2=val2&key3=val3#stuff") +## |> Url.path +## ``` +## +## ``` +## # Gives "/foo/" +## Url.fromStr("/foo/?key1=val1&key2=val2&key3=val3#stuff") +## |> Url.path +## ``` +path : Url -> Str +path = \@Url urlStr -> + withoutAuthority = + when Str.split_first urlStr ":" is + Ok { after } -> + when Str.split_first after "//" is + # Only drop the `//` if it's right after the `://` like in `https://` + # (so, `before` is empty) - otherwise, the `//` is part of the path! + Ok { before, after: afterSlashes } if Str.is_empty before -> afterSlashes + _ -> after + + # There's no `//` and also no `:` so this must be a path-only URL, e.g. "/foo?bar=baz#blah" + Err NotFound -> urlStr + + # Drop the query and/or fragment + when Str.split_last withoutAuthority "?" is + Ok { before } -> before + Err NotFound -> + when Str.split_last withoutAuthority "#" is + Ok { before } -> before + Err NotFound -> withoutAuthority From e872d958b6c983e4f62145a61d778335e38030fb Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Sat, 11 Jan 2025 11:47:31 +1100 Subject: [PATCH 13/20] fix Result.map_err --- platform/main.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/main.roc b/platform/main.roc index 9c35d146..dc231fd8 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -44,8 +44,8 @@ init_for_host! = \_ -> Program exited with error: $(Inspect.to_str(err)) - Tip: If you do not want to exit on this error, use `Result.mapErr` to handle the error. - Docs for `Result.mapErr`: + Tip: If you do not want to exit on this error, use `Result.map_err` to handle the error. + Docs for `Result.map_err`: """, ) Err(1) From 0a80e39bae8753c95d1a3b5486aece8176021cb4 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Sat, 11 Jan 2025 12:07:58 +1100 Subject: [PATCH 14/20] update flake --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 4fa25636..9e19870b 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1732722421, - "narHash": "sha256-HRJ/18p+WoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg=", + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "type": "github" }, "original": { @@ -102,11 +102,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1736460403, - "narHash": "sha256-Q+CVTjdbBCBLyoRQJ1K5VIun+03mlYo2LuplyYeWw4Q=", + "lastModified": 1736551512, + "narHash": "sha256-FOk4N9D6DpinJE+8mqopC0yNaY7WYBkcppN5q+VNVoE=", "owner": "roc-lang", "repo": "roc", - "rev": "a69a326161a49c19186b8bf07b50c625d3b95686", + "rev": "528d1d2b6944e3b51ce00a0be63051d46a723c6e", "type": "github" }, "original": { @@ -134,11 +134,11 @@ ] }, "locked": { - "lastModified": 1732802692, - "narHash": "sha256-kFrxb45qj52TT/OFUFyTdmvXkn/KXDUL0/DOtjHEQvs=", + "lastModified": 1736303309, + "narHash": "sha256-IKrk7RL+Q/2NC6+Ql6dwwCNZI6T6JH2grTdJaVWHF0A=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "34971069ec33755b2adf2481851f66d8ec9a6bfa", + "rev": "a0b81d4fa349d9af1765b0f0b4a899c13776f706", "type": "github" }, "original": { @@ -154,11 +154,11 @@ ] }, "locked": { - "lastModified": 1736303309, - "narHash": "sha256-IKrk7RL+Q/2NC6+Ql6dwwCNZI6T6JH2grTdJaVWHF0A=", + "lastModified": 1736476219, + "narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a0b81d4fa349d9af1765b0f0b4a899c13776f706", + "rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9", "type": "github" }, "original": { From 0c33444c88b04874bc0cce4c4bb7613ba16f73a8 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Sat, 11 Jan 2025 12:08:13 +1100 Subject: [PATCH 15/20] upgrade new string interpolation syntax --- build.roc | 18 +++++++++--------- examples/command.roc | 14 ++++++++------ examples/dir.roc | 6 +++--- examples/echo.roc | 2 +- examples/env.roc | 2 +- examples/error-handling.roc | 8 ++++---- examples/file-upload-form.roc | 2 +- examples/file.roc | 6 +++--- examples/hello-web.roc | 2 +- examples/init-basic.roc | 4 ++-- examples/sqlite.roc | 4 ++-- examples/temp-dir.roc | 2 +- examples/todos.db-wal | Bin 8272 -> 24752 bytes examples/todos.roc | 10 +++++----- platform/Cmd.roc | 2 +- platform/Env.roc | 2 +- platform/InternalDateTime.roc | 10 +++++----- platform/SplitList.roc | 2 +- platform/Sqlite.roc | 2 +- platform/Tcp.roc | 8 ++++---- platform/Url.roc | 8 ++++---- platform/main.roc | 4 ++-- 22 files changed, 60 insertions(+), 58 deletions(-) diff --git a/build.roc b/build.roc index 74087bea..fcec1f93 100644 --- a/build.roc +++ b/build.roc @@ -46,7 +46,7 @@ run! = \maybe_roc -> os_and_arch = get_os_and_arch!({})? - stub_lib_path = "platform/libapp.$(stub_file_extension(os_and_arch))" + stub_lib_path = "platform/libapp.${stub_file_extension(os_and_arch)}" build_stub_app_lib!(roc_cmd, stub_lib_path)? @@ -63,7 +63,7 @@ run! = \maybe_roc -> roc_version! : Str => Result {} _ roc_version! = \roc_cmd -> - info!("Checking provided roc; executing `$(roc_cmd) version`:")? + info!("Checking provided roc; executing `${roc_cmd} version`:")? Cmd.exec!(roc_cmd, ["version"]) |> Result.map_err(RocVersionCheckFailed) @@ -90,10 +90,10 @@ get_rust_target_folder! = \{} -> if Str.is_empty(target_env_var) then Ok("target/release/") else - Ok("target/$(target_env_var)/release/") + Ok("target/${target_env_var}/release/") Err(e) -> - info!("Failed to get env var CARGO_BUILD_TARGET with error $(Inspect.to_str(e)). Assuming default CARGO_BUILD_TARGET (native)...")? + info!("Failed to get env var CARGO_BUILD_TARGET with error ${Inspect.to_str(e)}. Assuming default CARGO_BUILD_TARGET (native)...")? Ok("target/release/") @@ -107,10 +107,10 @@ cargo_build_host! = \{} -> copy_host_lib! : OSAndArch, Str => Result {} _ copy_host_lib! = \os_and_arch, rust_target_folder -> - host_build_path = "$(rust_target_folder)libhost.a" - host_dest_path = "platform/$(prebuilt_static_lib_file(os_and_arch))" + host_build_path = "${rust_target_folder}libhost.a" + host_dest_path = "platform/${prebuilt_static_lib_file(os_and_arch)}" - info!("Moving the prebuilt binary from $(host_build_path) to $(host_dest_path) ...")? + info!("Moving the prebuilt binary from ${host_build_path} to ${host_dest_path} ...")? Cmd.exec!("cp", [host_build_path, host_dest_path]) |> Result.map_err(ErrMovingPrebuiltLegacyBinary) @@ -155,7 +155,7 @@ preprocess_host! = \roc_cmd, stub_lib_path, rust_target_folder -> info!("Preprocessing surgical host ...")? - surgical_build_path = "$(rust_target_folder)host" + surgical_build_path = "${rust_target_folder}host" roc_cmd |> Cmd.exec!(["preprocess-host", surgical_build_path, "platform/main.roc", stub_lib_path]) @@ -163,4 +163,4 @@ preprocess_host! = \roc_cmd, stub_lib_path, rust_target_folder -> info! : Str => Result {} _ info! = \msg -> - Stdout.line!("\u(001b)[34mINFO:\u(001b)[0m $(msg)") + Stdout.line!("\u(001b)[34mINFO:\u(001b)[0m ${msg}") diff --git a/examples/command.roc b/examples/command.roc index ec026e18..4f6b3ebf 100644 --- a/examples/command.roc +++ b/examples/command.roc @@ -15,10 +15,12 @@ respond! = \req, _ -> # Log request date, method and url using echo program datetime = Utc.to_iso_8601(Utc.now!({})) - Cmd.exec!("echo", ["$(datetime) $(Inspect.to_str(req.method)) $(req.uri)"])? + Cmd.exec!("echo", ["${datetime} ${Inspect.to_str(req.method)} ${req.uri}"])? - Ok({ - status: 200, - headers: [], - body: Str.to_utf8("Command succeeded."), - }) + Ok( + { + status: 200, + headers: [], + body: Str.to_utf8("Command succeeded."), + }, + ) diff --git a/examples/dir.roc b/examples/dir.roc index df144e8b..c67544ff 100644 --- a/examples/dir.roc +++ b/examples/dir.roc @@ -17,7 +17,7 @@ init! = \{} -> \CwdUnavailable -> Exit(1, "Unable to read current working directory"), )? - Stdout.line!("The current working directory is $(Path.display(cwd))")? + Stdout.line!("The current working directory is ${Path.display(cwd)}")? # Try to set cwd to examples Result.map_err( @@ -31,13 +31,13 @@ init! = \{} -> paths = Result.map_err( Dir.list!("./"), - \DirErr(err) -> Exit(1, "Error reading directory ./:\n\t$(Inspect.to_str(err))"), + \DirErr(err) -> Exit(1, "Error reading directory ./:\n\t${Inspect.to_str(err)}"), )? paths |> List.map(Path.display) |> Str.join_with(",") - |> \paths_str -> "The paths are;\n$(paths_str)" + |> \paths_str -> "The paths are;\n${paths_str}" |> Stdout.line! |> try diff --git a/examples/echo.roc b/examples/echo.roc index b1c25f99..e3c9aa6b 100644 --- a/examples/echo.roc +++ b/examples/echo.roc @@ -14,7 +14,7 @@ respond! = \req, _ -> # Log request datetime, method and url datetime = Utc.to_iso_8601(Utc.now!({})) - Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)")? + Stdout.line!("${datetime} ${Inspect.to_str(req.method)} ${req.uri}")? # Respond with request body if List.is_empty(req.body) then diff --git a/examples/env.roc b/examples/env.roc index 5de373e2..4ee81182 100644 --- a/examples/env.roc +++ b/examples/env.roc @@ -26,7 +26,7 @@ respond! = \_, debug -> body = vars |> Dict.to_list - |> List.map(\(k, v) -> "$(k): $(v)") + |> List.map(\(k, v) -> "${k}: ${v}") |> Str.join_with("\n") |> Str.concat("\n") |> Str.to_utf8 diff --git a/examples/error-handling.roc b/examples/error-handling.roc index 9884db7c..2cb8a995 100644 --- a/examples/error-handling.roc +++ b/examples/error-handling.roc @@ -23,9 +23,9 @@ AppError : [ map_app_err : AppError -> [ServerErr Str] map_app_err = \app_err -> when app_err is - EnvVarNotSet(env_var_name) -> ServerErr("Environment variable \"$(env_var_name)\" was not set.") - BadBody(err) -> ServerErr("Http error fetching content:\n\t$(Inspect.to_str(err))") - StdoutErr(err) -> ServerErr("Stdout error logging request:\n\t$(err)") + EnvVarNotSet(env_var_name) -> ServerErr("Environment variable \"${env_var_name}\" was not set.") + BadBody(err) -> ServerErr("Http error fetching content:\n\t${Inspect.to_str(err)}") + StdoutErr(err) -> ServerErr("Stdout error logging request:\n\t${err}") # Here we use AppError to ensure all errors must be handled within our application. handle_req! : Request => Result Response AppError @@ -46,7 +46,7 @@ log_request! : Request => Result {} [StdoutErr Str] log_request! = \req -> datetime = Utc.to_iso_8601(Utc.now!({})) - Stdout.line!("$(datetime) $(Inspect.to_str(req.method)) $(req.uri)") + Stdout.line!("${datetime} ${Inspect.to_str(req.method)} ${req.uri}") |> Result.map_err(\err -> StdoutErr(Inspect.to_str(err))) read_env_var! : Str => Result Str [EnvVarNotSet Str] diff --git a/examples/file-upload-form.roc b/examples/file-upload-form.roc index 51255020..286123d9 100644 --- a/examples/file-upload-form.roc +++ b/examples/file-upload-form.roc @@ -58,7 +58,7 @@ respond! = \req, _ ->