@@ -7,7 +7,9 @@ use crate::util::restricted_names::is_glob_pattern;
77use cargo:: core:: Verbosity ;
88use cargo:: core:: Workspace ;
99use cargo:: ops:: { self , CompileFilter , Packages } ;
10+ use cargo:: util:: closest;
1011use cargo_util:: ProcessError ;
12+ use itertools:: Itertools as _;
1113
1214pub fn cli ( ) -> Command {
1315 subcommand ( "run" )
@@ -97,11 +99,81 @@ pub fn is_manifest_command(arg: &str) -> bool {
9799}
98100
99101pub fn exec_manifest_command ( config : & mut Config , cmd : & str , args : & [ OsString ] ) -> CliResult {
100- if !config. cli_unstable ( ) . script {
101- return Err ( anyhow:: anyhow!( "running `{cmd}` requires `-Zscript`" ) . into ( ) ) ;
102+ let manifest_path = Path :: new ( cmd) ;
103+ match ( manifest_path. is_file ( ) , config. cli_unstable ( ) . script ) {
104+ ( true , true ) => { }
105+ ( true , false ) => {
106+ return Err ( anyhow:: anyhow!( "running the file `{cmd}` requires `-Zscript`" ) . into ( ) ) ;
107+ }
108+ ( false , true ) => {
109+ let possible_commands = crate :: list_commands ( config) ;
110+ let is_dir = if manifest_path. is_dir ( ) {
111+ format ! ( "\n \t `{cmd}` is a directory" )
112+ } else {
113+ "" . to_owned ( )
114+ } ;
115+ let suggested_command = if let Some ( suggested_command) = possible_commands
116+ . keys ( )
117+ . filter ( |c| cmd. starts_with ( c. as_str ( ) ) )
118+ . max_by_key ( |c| c. len ( ) )
119+ {
120+ let actual_args = cmd. strip_prefix ( suggested_command) . unwrap ( ) ;
121+ let args = if args. is_empty ( ) {
122+ "" . to_owned ( )
123+ } else {
124+ format ! (
125+ " {}" ,
126+ args. into_iter( ) . map( |os| os. to_string_lossy( ) ) . join( " " )
127+ )
128+ } ;
129+ format ! ( "\n \t Did you mean the command `{suggested_command} {actual_args}{args}`" )
130+ } else {
131+ "" . to_owned ( )
132+ } ;
133+ let suggested_script = if let Some ( suggested_script) = suggested_script ( cmd) {
134+ format ! ( "\n \t Did you mean the file `{suggested_script}`" )
135+ } else {
136+ "" . to_owned ( )
137+ } ;
138+ return Err ( anyhow:: anyhow!(
139+ "no such file or subcommand `{cmd}`{is_dir}{suggested_command}{suggested_script}"
140+ )
141+ . into ( ) ) ;
142+ }
143+ ( false , false ) => {
144+ // HACK: duplicating the above for minor tweaks but this will all go away on
145+ // stabilization
146+ let possible_commands = crate :: list_commands ( config) ;
147+ let suggested_command = if let Some ( suggested_command) = possible_commands
148+ . keys ( )
149+ . filter ( |c| cmd. starts_with ( c. as_str ( ) ) )
150+ . max_by_key ( |c| c. len ( ) )
151+ {
152+ let actual_args = cmd. strip_prefix ( suggested_command) . unwrap ( ) ;
153+ let args = if args. is_empty ( ) {
154+ "" . to_owned ( )
155+ } else {
156+ format ! (
157+ " {}" ,
158+ args. into_iter( ) . map( |os| os. to_string_lossy( ) ) . join( " " )
159+ )
160+ } ;
161+ format ! ( "\n \t Did you mean the command `{suggested_command} {actual_args}{args}`" )
162+ } else {
163+ "" . to_owned ( )
164+ } ;
165+ let suggested_script = if let Some ( suggested_script) = suggested_script ( cmd) {
166+ format ! ( "\n \t Did you mean the file `{suggested_script}` with `-Zscript`" )
167+ } else {
168+ "" . to_owned ( )
169+ } ;
170+ return Err ( anyhow:: anyhow!(
171+ "no such subcommand `{cmd}`{suggested_command}{suggested_script}"
172+ )
173+ . into ( ) ) ;
174+ }
102175 }
103176
104- let manifest_path = Path :: new ( cmd) ;
105177 let manifest_path = root_manifest ( Some ( manifest_path) , config) ?;
106178
107179 // Treat `cargo foo.rs` like `cargo install --path foo` and re-evaluate the config based on the
@@ -123,6 +195,39 @@ pub fn exec_manifest_command(config: &mut Config, cmd: &str, args: &[OsString])
123195 cargo:: ops:: run ( & ws, & compile_opts, args) . map_err ( |err| to_run_error ( config, err) )
124196}
125197
198+ fn suggested_script ( cmd : & str ) -> Option < String > {
199+ let cmd_path = Path :: new ( cmd) ;
200+ let mut suggestion = Path :: new ( "." ) . to_owned ( ) ;
201+ for cmd_part in cmd_path. components ( ) {
202+ let exact_match = suggestion. join ( cmd_part) ;
203+ suggestion = if exact_match. exists ( ) {
204+ exact_match
205+ } else {
206+ let possible: Vec < _ > = std:: fs:: read_dir ( suggestion)
207+ . into_iter ( )
208+ . flatten ( )
209+ . filter_map ( |e| e. ok ( ) )
210+ . map ( |e| e. path ( ) )
211+ . filter ( |p| p. to_str ( ) . is_some ( ) )
212+ . collect ( ) ;
213+ if let Some ( possible) = closest (
214+ cmd_part. as_os_str ( ) . to_str ( ) . unwrap ( ) ,
215+ possible. iter ( ) ,
216+ |p| p. file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ,
217+ ) {
218+ possible. to_owned ( )
219+ } else {
220+ return None ;
221+ }
222+ } ;
223+ }
224+ if suggestion. is_dir ( ) {
225+ None
226+ } else {
227+ suggestion. into_os_string ( ) . into_string ( ) . ok ( )
228+ }
229+ }
230+
126231fn to_run_error ( config : & cargo:: util:: Config , err : anyhow:: Error ) -> CliError {
127232 let proc_err = match err. downcast_ref :: < ProcessError > ( ) {
128233 Some ( e) => e,
0 commit comments