Skip to content

Commit

Permalink
RFC: add a command to help parsing arguments in scripts (#875)
Browse files Browse the repository at this point in the history
the other day, i was writing a script and wanted to pass a `list<int>`
to it which is not possible because there is no such things as _types_
for externals 🤔

i ended up writing an "arg parsing" command to help in that task and
thought it could be useful to people 😇

in this MR, i add `std-rfc parse-arg` in the `script-parsing.nu` module
and add associated tests which all pass.

i invite the reader to have a look at the docstring of `parse-arg` which
should contain a full example explaining the usage of this new command
😉

---------

Co-authored-by: Darren Schroeder <[email protected]>
  • Loading branch information
amtoine and fdncred committed Jul 21, 2024
1 parent 6aa2700 commit 71f5736
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 0 deletions.
1 change: 1 addition & 0 deletions stdlib-candidate/std-rfc/mod.nu
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export module math/
export use bulk-rename.nu *
export use set-env.nu *
export use bench.nu
export use script-parsing.nu [ parse-arg ]
61 changes: 61 additions & 0 deletions stdlib-candidate/std-rfc/script-parsing.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# helps parsing CLI arguments for Nushell scripts
#
# the following Nushell script does not make sense to be used as an external
# command because there is no such thing as a `list<string>` in Bash for
# instance.
# ```nushell
# def main [x: list<int>] {
# print $x
# }
# ```
#
# one needs to write something less strict at parse-time and thus looses type
# information...
# ```nushell
# def main [
# x: string, # list<int>
# ] {
# print $x
# }
# ```
#
# it's possible to write a much stronger script whith `parse-arg`
# ```nushell
# def main [
# x: string, # list<int>
# ] {
# let x = $x | parse-arg (metadata $x).span "list<int>" # the script would crash if either
# # `$x: string` is not valid NUON or if
# # the resulting value is not a `list<int>`
# print $x # here, `$x` is a `list<int>` as intended
# }
# ```
export def parse-arg [
span: record<start: int, end: int>, # the span of the input variable
expected_type: string, # the expected type for the input variable
]: [ string -> any ] {
let val = try {
$in | from nuon
} catch {
error make {
msg: $"(ansi red_bold)invalid NUON(ansi reset)",
label: {
text: "invalid NUON",
span: $span,
},
}
}

if ($val | describe) != $expected_type {
error make {
msg: $"(ansi red_bold)bad type(ansi reset)",
label: {
text: $"type: ($val | describe)",
span: $span,
},
help: $"expected ($expected_type)",
}
}

$val
}
1 change: 1 addition & 0 deletions stdlib-candidate/tests/mod.nu
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export module record.nu
export module str_xpend.nu
export module math.nu
export module bench.nu
export module script-parsing.nu
export module str_dedent.nu
37 changes: 37 additions & 0 deletions stdlib-candidate/tests/script-parsing.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std assert
use ../std-rfc parse-arg

const SPAN = { start: 0, end: 0 }

export def "test parse-arg ok" [] {
const TEST_CASES = [
[ input, type, expected ];

[ "123", "int", 123 ],
[ "[1, 2, 3]", "list<int>", [1, 2, 3] ],
[ "'spam'", "string", "spam" ],
[
"{ a: 1, b: 'egg', c: false }",
"record<a: int, b: string, c: bool>",
{ a: 1, b: 'egg', c: false },
],
]

for t in $TEST_CASES {
assert equal ($t.input | parse-arg $SPAN $t.type) $t.expected
}
}

export def "test parse-arg err" [] {
const TEST_CASES = [
[ input, type ];

[ "{ invalid NUON", "" ],
[ "[1, 2, 3]", "string" ],
]

for t in $TEST_CASES {
let msg = $"test case: input: '($t.input)', type: ($t.type)"
assert error { $t.input | parse-arg $SPAN $t.type } $msg
}
}

0 comments on commit 71f5736

Please sign in to comment.