diff --git a/examples/error-handling.roc b/examples/error-handling.roc index b2888190..22ca0868 100644 --- a/examples/error-handling.roc +++ b/examples/error-handling.roc @@ -1,8 +1,9 @@ -## Handle common filesystem errors with tag-pattern matching. +## Handle errors with tag-pattern matching. app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr import pf.Stdout +import pf.Stderr import pf.Path main! : List(OsStr) => Try({}, _) @@ -15,20 +16,22 @@ main! = |_args| { missing_file : Path missing_file = "nonexistent-file.txt" + # For professional software, use error tags (like `PathErr(NotFound)`) internally and convert + # them to a message for the user at the edge of your program. This also makes it easy to provide + # error messages in different languages. match missing_file.read_utf8!() { Ok(content) => Err(UnexpectedReadSuccess(content))? - Err(PathErr(NotFound)) => Stdout.line!("Expected error: Path not found (NotFound)")? - Err(PathErr(PermissionDenied)) => Stdout.line!("Error: Permission denied")? - Err(PathErr(Other(msg))) => Stdout.line!("Error: ${msg}")? - Err(_) => Stdout.line!("Error: Other file error")? + Err(PathErr(NotFound)) => Stderr.line!("Expected error: Path not found (NotFound)")? + Err(PathErr(PermissionDenied)) => Stderr.line!("Error: Permission denied")? + Err(PathErr(Other(msg))) => Stderr.line!("Error: ${msg}")? + Err(_) => Stderr.line!("Error: Other file error")? } - # Filesystem kind mismatches are portable typed errors. directory : Path directory = "examples" match directory.read_bytes!() { - Err(PathErr(IsADirectory)) => Stdout.line!("Expected error: Path is a directory (IsADirectory)")? + Err(PathErr(IsADirectory)) => Stderr.line!("Expected error: Path is a directory (IsADirectory)")? Ok(_) => Err(UnexpectedDirectoryReadSuccess)? Err(err) => Err(UnexpectedDirectoryReadError(err))? } @@ -37,7 +40,7 @@ main! = |_args| { regular_file = "LICENSE" match regular_file.list!() { - Err(PathErr(NotADirectory)) => Stdout.line!("Expected error: Path is not a directory (NotADirectory)")? + Err(PathErr(NotADirectory)) => Stderr.line!("Expected error: Path is not a directory (NotADirectory)")? Ok(_) => Err(UnexpectedFileListSuccess)? Err(err) => Err(UnexpectedFileListError(err))? } diff --git a/examples/file-read-buffered.roc b/examples/file-read-buffered.roc index a336959b..79f8907e 100644 --- a/examples/file-read-buffered.roc +++ b/examples/file-read-buffered.roc @@ -1,4 +1,8 @@ ## Read a file incrementally with a buffer instead of loading it all at once. +## +## This can be useful to process large files without using a lot of RAM or +## requiring the user to wait until the complete file is processed when they +## only wanted to look at the first page. app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr diff --git a/examples/http.roc b/examples/http-simple.roc similarity index 77% rename from examples/http.roc rename to examples/http-simple.roc index 16c57bca..a23dbc57 100644 --- a/examples/http.roc +++ b/examples/http-simple.roc @@ -8,7 +8,6 @@ import pf.OsStr import pf.Http import pf.Stdout import http.Request -import http.Response main! : List(OsStr) => Try({}, _) main! = |_args| { @@ -27,11 +26,5 @@ main! = |_args| { Stdout.line!("Response body:")? Stdout.line!(body)? - response_2 = Http.send!(Request.from_method(GET).with_uri("http://127.0.0.1:9000/html")) ? |err| SendSecondHtmlFailed(err) - body_2 = Str.from_utf8(Response.body(response_2)) ? |err| SecondHtmlBodyUtf8Failed(err) - - Stdout.line!("Response body 2:")? - Stdout.line!(body_2)? - Ok({}) } diff --git a/examples/path.roc b/examples/path.roc index 1525e216..701d38d3 100644 --- a/examples/path.roc +++ b/examples/path.roc @@ -1,4 +1,4 @@ -## Inspect a path's filename, extension, representation, and filesystem type. +## Inspect a path's filename, extension, string representation, and type (file/dir/symlink). app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr exposing [OsStr] @@ -8,7 +8,7 @@ import pf.Path main! : List(OsStr) => Try({}, _) main! = |args| { path = path_argument(args)? - filename = Path.filename(path).map_ok(Path.display) ?? "" + filename = Path.filename(path).map_ok(Path.display) ?? "Path is not a file." extension = Path.ext(path).map_ok(Path.display) ?? "" Stdout.line!( diff --git a/examples/print.roc b/examples/print.roc index 14c6bec0..97b2699f 100644 --- a/examples/print.roc +++ b/examples/print.roc @@ -1,4 +1,4 @@ -## Write lines, text, and lists to standard output and standard error. +## Write text, lines and lists to standard output and standard error. app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr @@ -7,22 +7,26 @@ import pf.Stderr main! : List(OsStr) => Try({}, _) main! = |_args| { - # Print a string to stdout + # Print a string to stdout with a newline Stdout.line!("Hello, world!")? # Print without a newline Stdout.write!("No newline after me.")? - # Print a string to stderr + # Print a string to stderr with a newline Stderr.line!("Hello, error!")? # Print a string to stderr without a newline Stderr.write!("Err with no newline after.")? # Print a list to stdout - for str in ["Foo", "Bar", "Baz"] { + lst = ["Foo", "Bar", "Baz"] + for str in lst { Stdout.line!(str)? } + # Use inspect to convert a value into a nice string representation for debugging + Stdout.line!(Str.inspect(lst))? + Ok({}) } diff --git a/examples/random.roc b/examples/random.roc index db02d73d..e9f08adf 100644 --- a/examples/random.roc +++ b/examples/random.roc @@ -13,5 +13,7 @@ main! = |_args| { random_u32 = Random.seed_u32!()? Stdout.line!("Random U32 seed is: ${random_u32.to_str()}")? + # TODO link to example showing how to use the seed values to generate random numbers + Ok({}) } diff --git a/examples/sqlite-basic.roc b/examples/sqlite-basic.roc index bb729bc5..868a83a8 100644 --- a/examples/sqlite-basic.roc +++ b/examples/sqlite-basic.roc @@ -1,4 +1,4 @@ -## Query a SQLite database and decode rows into application records. +## Query a SQLite database and decode rows into records. app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr @@ -50,6 +50,7 @@ print_line! : Str => Try({}, _) print_line! = |line| Stdout.line!(line) query_todos_by_status! = |db_path, status| +# `many` when you expect multiple rows to be returned. Sqlite.query_many!({ path: db_path, query: "SELECT id, task, status FROM todos WHERE status = :status;", @@ -58,7 +59,7 @@ query_todos_by_status! = |db_path, status| }) # A row decoder is `List(Str) -> (Stmt => Try(a, err))`; the new compiler does not -# support the record-builder (`<-`) sugar, so we combine the leaf decoders by hand. +# support the old record-builder (`<-`) sugar, so we combine the leaf decoders by hand. decode_todo = |cols| |stmt| { id = Sqlite.i64("id")(cols)(stmt)? diff --git a/examples/sqlite-everything.roc b/examples/sqlite-everything.roc index 2bd74e13..01be9e86 100644 --- a/examples/sqlite-everything.roc +++ b/examples/sqlite-everything.roc @@ -1,4 +1,4 @@ -## Exercise SQLite queries, decoders, nullable values, and prepared writes. +## Shows SQLite queries, decoders, nullable values, and prepared writes. app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" } import pf.OsStr @@ -85,6 +85,9 @@ run! = || { ", ", ) + # Bindings map each named placeholder (like :task0) in the query to its actual + # value. Passing values this way lets SQLite handle escaping and type conversion, + # which avoids SQL injection and quoting bugs from building the query as a string. binding_groups = List.map_with_index( todos_list, |t, indx| { @@ -148,7 +151,8 @@ run! = || { }) ? |err| PrepareUpdateTodoFailed(err) prepared_update.execute!([{ name: ":id", value: Integer(1) }]) ? |err| ExecutePreparedUpdateFailed(err) - # Reuse verifies that execution resets the statement before returning. + # You can run the same prepared statement again with different values. + # Each execution starts fresh, so the previous run's values don't carry over. prepared_update.execute!([{ name: ":id", value: Integer(2) }]) ? |err| ReusePreparedUpdateFailed(err) prepared_count = Sqlite.prepare!({ @@ -165,7 +169,7 @@ run! = || { }) ? |err| PrepareSortedTodosFailed(err) todos_sorted = prepared_query.query_many!([], decode_task_status) ? |err| QuerySortedTodosFailed(err) - # Reuse verifies that querying resets the statement before returning. + # Note that the result is not cached if we run the same prepared query again. todos_sorted_again = prepared_query.query_many!([], decode_task_status) ? |err| ReuseSortedTodosFailed(err) expect todos_sorted_again.len() == todos_sorted.len() diff --git a/scripts/test_spec.json b/scripts/test_spec.json index 2bcac8e5..25789463 100644 --- a/scripts/test_spec.json +++ b/scripts/test_spec.json @@ -198,7 +198,7 @@ ] }, { - "path": "examples/http.roc", + "path": "examples/http-simple.roc", "cases": [ { "name": "server-available",