Skip to content

Commit f0b1136

Browse files
committed
fix command.roc, misc improvements
1 parent f3d8287 commit f0b1136

11 files changed

Lines changed: 188 additions & 117 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ repository = "https://github.com/roc-lang/basic-cli"
4040

4141
[workspace.dependencies]
4242
# Core Roc types
43-
# roc-nightly: 2026-03-06
44-
roc_std_new = { git = "https://github.com/roc-lang/roc", rev = "a02d2287afd24d9f13baff3a5c6059e795b01661" }
43+
# roc-nightly: 2026-03-30
44+
roc_std_new = { git = "https://github.com/roc-lang/roc", rev = "c593bfd81918bde53955fd0f5ad964ce479d521f" }
4545

4646
# Internal crates
4747
roc_io_error = { path = "crates/roc_io_error" }

ci/all_tests.sh

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,50 +43,56 @@ cleanup() {
4343
# Set up trap to ensure cleanup runs on exit
4444
trap cleanup EXIT
4545

46-
# Get nightly version info from Cargo.toml
47-
source ci/get_roc_nightly_url.sh
48-
NEED_DOWNLOAD=false
49-
5046
echo "=== basic-cli CI ==="
5147
echo ""
5248

53-
# Check if cached roc exists and matches pinned version
54-
# Derive directory name from archive name (strip .tar.gz or .zip extension)
55-
ROC_DIR="${ROC_NIGHTLY_ARCHIVE%.tar.gz}"
56-
ROC_DIR="${ROC_DIR%.zip}"
57-
if [ -d "$ROC_DIR" ] && [ -f "$ROC_DIR/roc" ]; then
58-
CACHED_VERSION=$("./$ROC_DIR/roc" version 2>/dev/null || echo "unknown")
59-
if echo "$CACHED_VERSION" | grep -q "$ROC_NIGHTLY_COMMIT"; then
60-
echo "roc already at correct version: $CACHED_VERSION"
49+
if [ -n "${ROC:-}" ]; then
50+
# Use user-specified roc binary
51+
ROC_BIN_DIR="$(cd "$(dirname "$ROC")" && pwd)"
52+
export PATH="$ROC_BIN_DIR:$PATH"
53+
else
54+
# Get nightly version info from Cargo.toml
55+
source ci/get_roc_nightly_url.sh
56+
NEED_DOWNLOAD=false
57+
58+
# Check if cached roc exists and matches pinned version
59+
# Derive directory name from archive name (strip .tar.gz or .zip extension)
60+
ROC_DIR="${ROC_NIGHTLY_ARCHIVE%.tar.gz}"
61+
ROC_DIR="${ROC_DIR%.zip}"
62+
if [ -d "$ROC_DIR" ] && [ -f "$ROC_DIR/roc" ]; then
63+
CACHED_VERSION=$("./$ROC_DIR/roc" version 2>/dev/null || echo "unknown")
64+
if echo "$CACHED_VERSION" | grep -q "$ROC_NIGHTLY_COMMIT"; then
65+
echo "roc already at correct version: $CACHED_VERSION"
66+
else
67+
echo "Cached roc ($CACHED_VERSION) doesn't match nightly ($ROC_NIGHTLY_COMMIT)"
68+
echo "Removing stale roc directory..."
69+
rm -rf "$ROC_DIR"
70+
NEED_DOWNLOAD=true
71+
fi
6172
else
62-
echo "Cached roc ($CACHED_VERSION) doesn't match nightly ($ROC_NIGHTLY_COMMIT)"
63-
echo "Removing stale roc directory..."
64-
rm -rf "$ROC_DIR"
6573
NEED_DOWNLOAD=true
6674
fi
67-
else
68-
NEED_DOWNLOAD=true
69-
fi
7075

71-
if [ "$NEED_DOWNLOAD" = true ]; then
72-
echo "Downloading Roc nightly $ROC_NIGHTLY_COMMIT..."
73-
echo "URL: $ROC_NIGHTLY_URL"
76+
if [ "$NEED_DOWNLOAD" = true ]; then
77+
echo "Downloading Roc nightly $ROC_NIGHTLY_COMMIT..."
78+
echo "URL: $ROC_NIGHTLY_URL"
7479

75-
# Clean up any old nightly directories
76-
rm -rf roc_nightly-*
80+
# Clean up any old nightly directories
81+
rm -rf roc_nightly-*
7782

78-
curl -fOL "$ROC_NIGHTLY_URL"
79-
tar -xzf "$ROC_NIGHTLY_ARCHIVE"
80-
rm -f "$ROC_NIGHTLY_ARCHIVE"
83+
curl -fOL "$ROC_NIGHTLY_URL"
84+
tar -xzf "$ROC_NIGHTLY_ARCHIVE"
85+
rm -f "$ROC_NIGHTLY_ARCHIVE"
8186

82-
# Add to GITHUB_PATH if running in CI
83-
if [ -n "${GITHUB_PATH:-}" ]; then
84-
echo "$(pwd)/$ROC_DIR" >> "$GITHUB_PATH"
87+
# Add to GITHUB_PATH if running in CI
88+
if [ -n "${GITHUB_PATH:-}" ]; then
89+
echo "$(pwd)/$ROC_DIR" >> "$GITHUB_PATH"
90+
fi
8591
fi
86-
fi
8792

88-
# Ensure roc is in PATH
89-
export PATH="$(pwd)/$ROC_DIR:$PATH"
93+
# Ensure roc is in PATH
94+
export PATH="$(pwd)/$ROC_DIR:$PATH"
95+
fi
9096

9197
echo ""
9298
echo "Using roc version: $(roc version)"

ci/expect_scripts/path.exp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ cd $env(EXAMPLES_DIR)
1111
spawn ./path
1212

1313
set expected_output [normalize_output {
14-
is_file: Try.Ok(True)
15-
is_dir: Try.Ok(False)
16-
is_sym_link: Try.Ok(False)
14+
is_file: Ok(True)
15+
is_dir: Ok(False)
16+
is_sym_link: Ok(False)
1717
}]
1818

1919
expect $expected_output {

examples/command.roc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ main! = |_args| {
2020
# To run a command with environment variables.
2121
Cmd.new("env")
2222
.clear_envs() # You probably don't need to clear all other environment variables, this is just an example.
23+
.env("BAZ", "DUCK")
2324
.env("FOO", "BAR")
24-
.envs([("BAZ", "DUCK"), ("XYZ", "ABC")]) # Set multiple environment variables at once with `envs`
25-
.args(["-v"])
25+
.env("XYZ", "ABC")
2626
.exec_cmd!()?
2727

2828
# To execute and just get the exit code (prints to your terminal).

platform/Cmd.roc

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ Cmd :: {
208208
## ```
209209
env : Cmd, Str, Str -> Cmd
210210
env = |cmd, key, value| {
211-
..cmd,
212-
envs: cmd.envs.concat([key, value]),
211+
new_envs = cmd.envs.append(key).append(value)
212+
{ args: cmd.args, clear_envs: cmd.clear_envs, envs: new_envs, program: cmd.program }
213213
}
214214

215215
## Add multiple environment variables to the command.
@@ -219,11 +219,8 @@ Cmd :: {
219219
## ```
220220
envs : Cmd, List((Str, Str)) -> Cmd
221221
envs = |cmd, pairs| {
222-
flat = pairs.fold([], |acc, (k, v)| acc.concat([k, v]))
223-
{
224-
..cmd,
225-
envs: cmd.envs.concat(flat),
226-
}
222+
new_envs = flatten_str_pairs(pairs, cmd.envs, 0)
223+
{ args: cmd.args, clear_envs: cmd.clear_envs, envs: new_envs, program: cmd.program }
227224
}
228225

229226
## Clear all environment variables before running the command.
@@ -266,6 +263,20 @@ host_exec_exit_code! : Cmd => Try(I32, IOErr)
266263
# TODO use OutputFromHostSuccess and OutputFromHostFailure once #9216 is fixed
267264
host_exec_output! : Cmd => Try({stderr_bytes : List(U8), stdout_bytes : List(U8)}, (Try({stderr_bytes : List(U8), stdout_bytes : List(U8), exit_code : I32}, IOErr)))
268265

266+
flatten_str_pairs : List((Str, Str)), List(Str), U64 -> List(Str)
267+
flatten_str_pairs = |pairs, acc, idx| {
268+
if idx >= pairs.len() {
269+
acc
270+
} else {
271+
match pairs.get(idx) {
272+
Ok(pair) =>
273+
flatten_str_pairs(pairs, acc.append(pair.0).append(pair.1), idx + 1)
274+
Err(_) =>
275+
acc
276+
}
277+
}
278+
}
279+
269280
to_str : Cmd -> Str
270281
to_str = |cmd| {
271282
my_trim = |trimmed_str| {if trimmed_str.is_empty() "" else "envs: ${trimmed_str}"}

platform/Stderr.roc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import IOErr exposing [IOErr]
22

3+
## See Stdout.roc for explanation of why hosted functions use IOErr directly.
4+
host_stderr_line! : Str => Try({}, IOErr)
5+
host_stderr_write! : Str => Try({}, IOErr)
6+
host_stderr_write_bytes! : List(U8) => Try({}, IOErr)
7+
38
Stderr := [].{
49
## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)),
510
## followed by a newline.
611
##
712
## > To write to `stderr` without the newline, see [Stderr.write!].
813
line! : Str => Try({}, [StderrErr(IOErr), ..])
14+
line! = |msg| {
15+
match host_stderr_line!(msg) {
16+
Ok(val) => Ok(val)
17+
Err(ioerr) => Err(StderrErr(ioerr))
18+
}
19+
}
920

1021
## Write the given string to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)).
1122
##
@@ -14,10 +25,22 @@ Stderr := [].{
1425
##
1526
## > To write to `stderr` with a newline at the end, see [Stderr.line!].
1627
write! : Str => Try({}, [StderrErr(IOErr), ..])
28+
write! = |msg| {
29+
match host_stderr_write!(msg) {
30+
Ok(val) => Ok(val)
31+
Err(ioerr) => Err(StderrErr(ioerr))
32+
}
33+
}
1734

1835
## Write the given bytes to [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)).
1936
##
2037
## Most terminals will not actually display content that are written to them until they receive a newline,
2138
## so this may appear to do nothing until you write a newline!
2239
write_bytes! : List(U8) => Try({}, [StderrErr(IOErr), ..])
40+
write_bytes! = |bytes| {
41+
match host_stderr_write_bytes!(bytes) {
42+
Ok(val) => Ok(val)
43+
Err(ioerr) => Err(StderrErr(ioerr))
44+
}
45+
}
2346
}

platform/Stdin.roc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import IOErr exposing [IOErr]
22

3+
## See Stdout.roc for explanation of why hosted functions use closed error types.
4+
host_stdin_line! : {} => Try(Str, [EndOfFile, StdinErr(IOErr)])
5+
host_stdin_bytes! : {} => Try(List(U8), [EndOfFile, StdinErr(IOErr)])
6+
host_stdin_read_to_end! : {} => Try(List(U8), IOErr)
7+
38
Stdin := [].{
49
## Read a line from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)).
510
##
@@ -8,6 +13,13 @@ Stdin := [].{
813
## program having gotten stuck. It's often helpful to print a prompt first, so
914
## the user knows it's necessary to enter something before the program will continue.
1015
line! : {} => Try(Str, [EndOfFile, StdinErr(IOErr), ..])
16+
line! = |arg| {
17+
match host_stdin_line!(arg) {
18+
Ok(val) => Ok(val)
19+
Err(EndOfFile) => Err(EndOfFile)
20+
Err(StdinErr(ioerr)) => Err(StdinErr(ioerr))
21+
}
22+
}
1123

1224
## Read bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)).
1325
## This function can read no more than 16,384 bytes at a time. Use [read_to_end!] if you need more.
@@ -16,8 +28,21 @@ Stdin := [].{
1628
## which disables defaults terminal bevahiour and allows reading input
1729
## without buffering until Enter key is pressed.
1830
bytes! : {} => Try(List(U8), [EndOfFile, StdinErr(IOErr), ..])
31+
bytes! = |arg| {
32+
match host_stdin_bytes!(arg) {
33+
Ok(val) => Ok(val)
34+
Err(EndOfFile) => Err(EndOfFile)
35+
Err(StdinErr(ioerr)) => Err(StdinErr(ioerr))
36+
}
37+
}
1938

2039
## Read all bytes from [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin))
2140
## until [EOF](https://en.wikipedia.org/wiki/End-of-file) in this source.
2241
read_to_end! : {} => Try(List(U8), [StdinErr(IOErr), ..])
42+
read_to_end! = |arg| {
43+
match host_stdin_read_to_end!(arg) {
44+
Ok(val) => Ok(val)
45+
Err(ioerr) => Err(StdinErr(ioerr))
46+
}
47+
}
2348
}

platform/Stdout.roc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import IOErr exposing [IOErr]
22

3+
## These hosted functions return Try({}, IOErr) directly - matching the host's ABI.
4+
## They must NOT use open tag unions ([..]) because the compiler would extend them
5+
## with error variants from the calling context, changing the memory layout and
6+
## causing a mismatch with what the host actually writes.
7+
host_stdout_line! : Str => Try({}, IOErr)
8+
host_stdout_write! : Str => Try({}, IOErr)
9+
host_stdout_write_bytes! : List(U8) => Try({}, IOErr)
10+
311
Stdout := [].{
412
## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)),
513
## followed by a newline.
614
##
715
## > To write to `stdout` without the newline, see [Stdout.write!].
816
line! : Str => Try({}, [StdoutErr(IOErr), ..])
17+
line! = |msg| {
18+
match host_stdout_line!(msg) {
19+
Ok(val) => Ok(val)
20+
Err(ioerr) => Err(StdoutErr(ioerr))
21+
}
22+
}
923

1024
## Write the given string to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)).
1125
##
@@ -14,10 +28,22 @@ Stdout := [].{
1428
##
1529
## > To write to `stdout` with a newline at the end, see [Stdout.line!].
1630
write! : Str => Try({}, [StdoutErr(IOErr), ..])
31+
write! = |msg| {
32+
match host_stdout_write!(msg) {
33+
Ok(val) => Ok(val)
34+
Err(ioerr) => Err(StdoutErr(ioerr))
35+
}
36+
}
1737

1838
## Write the given bytes to [standard output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)).
1939
##
2040
## Note that many terminals will not actually display content that is written to them until they receive a newline,
2141
## so this may appear to do nothing until you write a newline!
2242
write_bytes! : List(U8) => Try({}, [StdoutErr(IOErr), ..])
43+
write_bytes! = |bytes| {
44+
match host_stdout_write_bytes!(bytes) {
45+
Ok(val) => Ok(val)
46+
Err(ioerr) => Err(StdoutErr(ioerr))
47+
}
48+
}
2349
}

0 commit comments

Comments
 (0)