11use anyhow:: Result ;
22use clap:: { CommandFactory , Parser , error:: ErrorKind } ;
33use log:: LevelFilter ;
4- use std:: { env, io:: Write , path:: Path } ;
4+ use std:: { env, ffi :: OsString , io:: Write , path:: Path } ;
55
66use rescript:: { build, cli, cmd, format, lock, watcher} ;
77
88fn main ( ) -> Result < ( ) > {
9- let raw_args: Vec < String > = env:: args ( ) . collect ( ) ;
9+ // Use `args_os` so non-UTF bytes still reach clap for proper error reporting on platforms that
10+ // allow arbitrary argv content.
11+ let raw_args: Vec < OsString > = env:: args_os ( ) . collect ( ) ;
1012 let cli = parse_cli ( raw_args) . unwrap_or_else ( |err| err. exit ( ) ) ;
1113
1214 let log_level_filter = cli. verbose . log_level_filter ( ) ;
@@ -113,14 +115,14 @@ fn get_lock(folder: &str) -> lock::Lock {
113115 }
114116}
115117
116- fn parse_cli ( raw_args : Vec < String > ) -> Result < cli:: Cli , clap:: Error > {
118+ fn parse_cli ( raw_args : Vec < OsString > ) -> Result < cli:: Cli , clap:: Error > {
117119 match cli:: Cli :: try_parse_from ( & raw_args) {
118120 Ok ( cli) => Ok ( cli) ,
119121 Err ( err) => {
120122 if should_default_to_build ( & err, & raw_args) {
121123 let mut fallback_args = raw_args. clone ( ) ;
122124 let insert_at = index_after_global_flags ( & fallback_args) ;
123- fallback_args. insert ( insert_at, "build" . into ( ) ) ;
125+ fallback_args. insert ( insert_at, OsString :: from ( "build" ) ) ;
124126
125127 match cli:: Cli :: try_parse_from ( & fallback_args) {
126128 Ok ( cli) => Ok ( cli) ,
@@ -133,7 +135,7 @@ fn parse_cli(raw_args: Vec<String>) -> Result<cli::Cli, clap::Error> {
133135 }
134136}
135137
136- fn should_default_to_build ( err : & clap:: Error , args : & [ String ] ) -> bool {
138+ fn should_default_to_build ( err : & clap:: Error , args : & [ OsString ] ) -> bool {
137139 match err. kind ( ) {
138140 ErrorKind :: MissingSubcommand
139141 | ErrorKind :: DisplayHelpOnMissingArgumentOrSubcommand
@@ -149,7 +151,7 @@ fn should_default_to_build(err: &clap::Error, args: &[String]) -> bool {
149151 }
150152}
151153
152- fn index_after_global_flags ( args : & [ String ] ) -> usize {
154+ fn index_after_global_flags ( args : & [ OsString ] ) -> usize {
153155 let mut idx = 1 ;
154156 while let Some ( arg) = args. get ( idx) {
155157 if is_global_flag ( arg) {
@@ -161,44 +163,52 @@ fn index_after_global_flags(args: &[String]) -> usize {
161163 idx. min ( args. len ( ) )
162164}
163165
164- fn is_global_flag ( arg : & str ) -> bool {
166+ fn is_global_flag ( arg : & OsString ) -> bool {
165167 matches ! (
166- arg,
167- "-v" | "-vv"
168- | "-vvv"
169- | "-vvvv"
170- | "-q"
171- | "-qq"
172- | "-qqq"
173- | "-qqqq"
174- | "--verbose"
175- | "--quiet"
176- | "-h"
177- | "--help"
178- | "-V"
179- | "--version"
168+ arg. to_str( ) ,
169+ Some (
170+ "-v" | "-vv"
171+ | "-vvv"
172+ | "-vvvv"
173+ | "-q"
174+ | "-qq"
175+ | "-qqq"
176+ | "-qqqq"
177+ | "--verbose"
178+ | "--quiet"
179+ | "-h"
180+ | "--help"
181+ | "-V"
182+ | "--version"
183+ )
180184 )
181185}
182186
183- fn first_non_global_arg ( args : & [ String ] ) -> Option < & str > {
184- args. iter ( )
185- . skip ( 1 )
186- . find ( |arg| !is_global_flag ( arg) )
187- . map ( |s| s. as_str ( ) )
187+ fn first_non_global_arg ( args : & [ OsString ] ) -> Option < & OsString > {
188+ args. iter ( ) . skip ( 1 ) . find ( |arg| !is_global_flag ( arg) )
188189}
189190
190- fn is_known_subcommand ( arg : & str ) -> bool {
191+ fn is_known_subcommand ( arg : & OsString ) -> bool {
192+ let Some ( arg_str) = arg. to_str ( ) else {
193+ return false ;
194+ } ;
195+
191196 cli:: Cli :: command ( ) . get_subcommands ( ) . any ( |subcommand| {
192- subcommand. get_name ( ) == arg || subcommand. get_all_aliases ( ) . any ( |alias| alias == arg )
197+ subcommand. get_name ( ) == arg_str || subcommand. get_all_aliases ( ) . any ( |alias| alias == arg_str )
193198 } )
194199}
195200
196201#[ cfg( test) ]
197202mod tests {
198203 use super :: * ;
204+ use std:: ffi:: OsString ;
199205
200206 fn parse ( args : & [ & str ] ) -> Result < cli:: Cli , clap:: Error > {
201- parse_cli ( args. iter ( ) . map ( |arg| arg. to_string ( ) ) . collect ( ) )
207+ parse_cli ( args. iter ( ) . map ( OsString :: from) . collect ( ) )
208+ }
209+
210+ fn parse_os ( args : Vec < OsString > ) -> Result < cli:: Cli , clap:: Error > {
211+ parse_cli ( args)
202212 }
203213
204214 #[ test]
@@ -245,4 +255,14 @@ mod tests {
245255 let err = parse ( & [ "rescript" , "--version" ] ) . expect_err ( "expected clap version error" ) ;
246256 assert_eq ! ( err. kind( ) , ErrorKind :: DisplayVersion ) ;
247257 }
258+
259+ #[ cfg( unix) ]
260+ #[ test]
261+ fn non_utf_argument_returns_error ( ) {
262+ use std:: os:: unix:: ffi:: OsStringExt ;
263+
264+ let args = vec ! [ OsString :: from( "rescript" ) , OsString :: from_vec( vec![ 0xff ] ) ] ;
265+ let err = parse_os ( args) . expect_err ( "expected clap to report invalid utf8" ) ;
266+ assert_eq ! ( err. kind( ) , ErrorKind :: InvalidUtf8 ) ;
267+ }
248268}
0 commit comments