Stream: beginners

Topic: Args for CLI platform?


view this post on Zulip David Dunn (Sep 14 2022 at 08:00):

Prompted by the session yesterday, I made a cowsay clone, rocsay.roc. You'll need the template roc.txt too. Put both of these in examples/interactive in the repo and run roc examples/interactive/rocsay.roc from the repo root. Here's a screenshot in case you don't want to run it.

Does the CLI platform allow reading arguments yet? I wanted to make rocsay take a string, like cowsay, but couldn't find anything in the CLI patform in the repo about taking command line args. I know the platform's new so it might not be available yet, but I wanted to check anyway.

view this post on Zulip jan kili (Sep 14 2022 at 12:10):

SO COOL! Yes, the example CLI platform should be getting arg parsing soon: https://github.com/roc-lang/roc/pull/3990

view this post on Zulip Richard Feldman (Sep 14 2022 at 13:08):

love it!!! :heart_eyes_cat:

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:35):

#3990 has landed, so the CLI platform now takes a main function that accepts command-line arguments!

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:36):

We've also added an command-line argument parser to the CLI platform, that you can use for all your CLI parsing needs. There's an example at https://github.com/roc-lang/roc/blob/main/examples/interactive/args.roc. Feel free to send suggest/file issues for feature requests and bugs for the arg parser!

view this post on Zulip David Dunn (Sep 14 2022 at 22:04):

Updated! Haven't use the fancy arg parsing yet, but I'll add some flags next. Here's the new code & a screenshot!

view this post on Zulip David Dunn (Sep 14 2022 at 22:13):

Thanks so much for the work on the args :pray: Looking forward to more happy hacking in Roc!

view this post on Zulip jan kili (Sep 14 2022 at 23:33):

I love seeing a one-day-old built-in (Str.replaceEach) used for such fun

view this post on Zulip Ghislain (Sep 15 2022 at 23:40):

Hi, I don't know if it's the best place, or if I should create a new channel.
After testing the new "args" feature of the "cli-platform", I came across this behavior when sending bad arguments:

$ ./examples/interactive/args log --num 3
[...]
Argument `--num` is required but was not provided!

I thought it would be a good opportunity to get into the implementation a bit more, and try to fix it. But I'm a bit stuck with the expect behavior that panics every time I want to do a failing test. And as I'm not very comfortable with the code yet, I'd like to do some changes in expects, for example, this will panic (from Arg.go:612):

# two string parsers complete cases
expect
    parser =
        succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)")
        |> withParser (str { long: "foo" })
        |> withParser (str { long: "bar" })

    cases = [
        ["--foo", "true"]
    ]

    List.all cases \args -> parseHelp parser args == Ok "foo: true bar: baz"

view this post on Zulip Ayaz Hafiz (Sep 16 2022 at 18:11):

@Folkert de Vries for the above, it looks like the ExpectFrame is being read at the wrong address and ends up corrupted - any ideas?

view this post on Zulip Ayaz Hafiz (Sep 16 2022 at 18:12):

@Ghislain in the meantime you can do something like

# two string parsers complete cases
expect
    parsed =
        parser =
            succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)")
            |> withParser (str { long: "foo" })
            |> withParser (str { long: "bar" })

        parseHelp parser ["--foo", "true"]

    parsed == Ok "foo: true bar: baz"

however you will notice that the error message is incorrect - it prints out the wrong error value :sweat_smile: so there is some reading of the wrong bytes there too!

view this post on Zulip Folkert de Vries (Sep 16 2022 at 18:45):

can we get that snippet down to a self-contained example?

view this post on Zulip Folkert de Vries (Sep 16 2022 at 18:45):

the failing one, I mean

view this post on Zulip Folkert de Vries (Sep 16 2022 at 18:59):

@Ayaz Hafiz I got it down to

Parser a := [
    Succeed a,
    Arg Config (List Str -> Result a [NotFound, WrongType]),
    # TODO: hiding the record behind an alias currently causes a panic
    SubCommand
        (List {
            name : Str,
            parser : Parser a,
        }),

    # Constructed during transformations of the above variants
    WithConfig (Parser a) Config,
    Lazy ({} -> a),
]

andMap : Parser a, Parser (a -> b) -> Parser b
andMap = \@Parser parser, @Parser mapper ->
    unwrapped =
        when mapper is
            Succeed fn ->
                when parser is
                    Succeed a ->
                        Lazy \{} -> fn a

                    Lazy thunk ->
                        Lazy \{} -> fn (thunk {})

                    WithConfig parser2 config ->
                        parser2
                        |> andMap (@Parser mapper)
                        |> WithConfig config

                    Arg config run ->
                        Arg config \args ->
                            run args
                            |> Result.map fn

                    SubCommand cmds ->
                        mapSubParser = \{ name, parser: parser2 } ->
                            { name, parser: andMap parser2 (@Parser mapper) }

                        List.map cmds mapSubParser
                        |> SubCommand

            Arg config run ->
                when parser is
                    Succeed a ->
                        Arg config \args ->
                            when run args is
                                Ok fn -> Ok (fn a)
                                Err err -> Err err

                    Lazy thunk ->
                        Arg config \args ->
                            when run args is
                                Ok fn -> Ok (fn (thunk {}))
                                Err err -> Err err

                    WithConfig parser2 config2 ->
                        parser2
                        |> andMap (@Parser mapper)
                        |> WithConfig config2

                    Arg config2 run2 ->
                        # Parse first the one and then the other.
                        combinedParser = Arg config2 \args ->
                            when run args is
                                Ok fn -> run2 args |> Result.map fn
                                Err err -> Err err

                        # Store the extra config.
                        @Parser combinedParser
                        |> WithConfig config

                    SubCommand cmds ->
                        # For each subcommand, first run the subcommand, then
                        # push the result through the arg parser.
                        mapSubParser = \{ name, parser: parser2 } ->
                            { name, parser: andMap parser2 (@Parser mapper) }

                        List.map cmds mapSubParser
                        |> SubCommand

            Lazy thunk ->
                fn = thunk {}

                when parser is
                    Succeed a ->
                        Lazy \{} -> fn a

                    Lazy innerThunk ->
                        Lazy \{} -> fn (innerThunk {})

                    WithConfig parser2 config ->
                        parser2
                        |> andMap (@Parser mapper)
                        |> WithConfig config

                    Arg config run ->
                        Arg config \args ->
                            run args
                            |> Result.map fn

                    SubCommand cmds ->
                        mapSubParser = \{ name, parser: parser2 } ->
                            { name, parser: andMap parser2 (@Parser mapper) }

                        List.map cmds mapSubParser
                        |> SubCommand

            WithConfig mapper2 config ->
                @Parser parser
                |> andMap mapper2
                |> WithConfig config

            SubCommand cmds ->
                mapSubParser = \{ name, parser: mapper2 } ->
                    { name, parser: andMap (@Parser parser) mapper2 }

                List.map cmds mapSubParser
                |> SubCommand

    @Parser unwrapped

withParser = \arg1, arg2 -> andMap arg2 arg1

succeed : a -> Parser a
succeed = \val -> @Parser (Succeed val)

# two string parsers complete cases
expect
    parser =
        succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)")
        |> withParser (str { long: "foo" })
        |> withParser (str { long: "bar" })

    cases = [
        ["--foo", "true"]
    ]

    List.all cases \args -> parseHelp parser args == Ok "foo: true bar: baz"

but when running that with roc test I get

── EXPECT PANICKED ──────────────────────── examples/benchmarks/Scratchpad.roc ─

This expectation crashed while running:

137│>  # two string parsers complete cases
138│>  expect
139│>      parser =
140│>          succeed (\foo -> \bar -> "foo: \(foo) bar: \(bar)")
141│>          |> withParser (str { long: "foo" })
142│>          |> withParser (str { long: "bar" })
143│>
144│>      cases = [
145│>          ["--foo", "true"]
146│>      ]
147│>
148│>      List.all cases \args -> parseHelp parser args == Ok "foo: true bar: baz"

The crash reported this message:

Hit an erroneous type when creating a layout for `#UserApp.succeed`

view this post on Zulip Folkert de Vries (Sep 16 2022 at 18:59):

so what we're seeing may be some interaction between type errors and the expect serialization not working in that case

view this post on Zulip Ayaz Hafiz (Sep 16 2022 at 19:18):

with the full file, there are no type errors but expect still fails. I'll try to cut down a minimal reproduction

view this post on Zulip Ayaz Hafiz (Sep 17 2022 at 00:28):

@Ghislain that bug should be fixed on the main branch now!

view this post on Zulip jan kili (Sep 26 2022 at 03:07):

Are positional arguments not yet supported?
For example: ./myApp echo 'hello world' instead of ./myApp echo --text 'hello world'

view this post on Zulip Ayaz Hafiz (Sep 26 2022 at 13:16):

They are not yet supported

view this post on Zulip jan kili (Sep 30 2022 at 04:58):

Ghislain said:

...
After testing the new "args" feature of the "cli-platform", I came across this behavior when sending bad arguments:
...

Good news - @Ayaz Hafiz seems to have fixed that!

view this post on Zulip jan kili (Sep 30 2022 at 04:59):

@rocco issue 4129

view this post on Zulip rocco (Sep 30 2022 at 04:59):

https://github.com/roc-lang/roc/issues/4129


Last updated: Jul 06 2025 at 12:14 UTC